fix(bridge): strip NODE_OPTIONS --inspect before spawning claude/gemini
claude-code and gemini-cli are both Node binaries. When the parent
gateway is launched with `NODE_OPTIONS=--inspect=127.0.0.1:9229` (for
debugging), spawn(child).env = {...process.env} propagates the flag into
the child. The child Node then tries to bind the same inspector port,
fails EADDRINUSE, and exits SILENTLY (no stdout, no stderr).
Bridge sees an empty stream and reports `claude did not return a
session_id` with an empty stderr summary — extremely opaque diagnostic
that took non-trivial digging to root-cause.
Sanitize NODE_OPTIONS before spawn: keep everything except
`--inspect*` / `--inspect-brk*` / `--debug*`. Operators that legitimately
need other NODE_OPTIONS values (e.g. `--max-old-space-size`) keep them.
Verified end-user repro on prod-t2 2026-05-31: with
`Environment=NODE_OPTIONS=--inspect=127.0.0.1:9229` in the gateway
systemd drop-in, `claude -p "hi" --output-format stream-json --verbose`
spawned from the bridge returned ZERO bytes; running the exact same
command from a shell without the env var returned the full init →
assistant → result stream in ~6s. Surfaced recruiting developer1
(Cody, contractor-claude-bridge).
This commit is contained in:
@@ -158,10 +158,29 @@ export async function* dispatchToClaude(
|
|||||||
// detached:true puts claude in its own process group. Claude's Bash tool
|
// detached:true puts claude in its own process group. Claude's Bash tool
|
||||||
// occasionally leaks shells/ssh that keep claude alive past end-of-turn; when
|
// occasionally leaks shells/ssh that keep claude alive past end-of-turn; when
|
||||||
// that happens we SIGKILL the whole group rather than wait forever.
|
// that happens we SIGKILL the whole group rather than wait forever.
|
||||||
|
// Sanitize NODE_OPTIONS before spawning. Claude Code is a Node CLI; if
|
||||||
|
// the parent gateway runs with `NODE_OPTIONS=--inspect=...:9229`, every
|
||||||
|
// child Node process — including claude — tries to bind the same inspector
|
||||||
|
// port, fails (EADDRINUSE), and exits SILENTLY (no stdout, no stderr).
|
||||||
|
// Bridge then sees an empty stream and reports `claude did not return a
|
||||||
|
// session_id` with no useful diagnostic. Strip any --inspect* /
|
||||||
|
// --inspect-brk* / --debug* flag from NODE_OPTIONS; keep everything else
|
||||||
|
// (e.g. --max-old-space-size) in case operators depend on it.
|
||||||
|
const childEnv: NodeJS.ProcessEnv = { ...process.env };
|
||||||
|
if (childEnv.NODE_OPTIONS) {
|
||||||
|
const filtered = childEnv.NODE_OPTIONS
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter((tok) => tok && !tok.startsWith("--inspect") && !tok.startsWith("--debug"))
|
||||||
|
.join(" ")
|
||||||
|
.trim();
|
||||||
|
if (filtered) childEnv.NODE_OPTIONS = filtered;
|
||||||
|
else delete childEnv.NODE_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
const child = spawn("claude", args, {
|
const child = spawn("claude", args, {
|
||||||
cwd: workspace,
|
cwd: workspace,
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
env: { ...process.env },
|
env: childEnv,
|
||||||
detached: true,
|
detached: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -156,10 +156,24 @@ export async function* dispatchToGemini(
|
|||||||
args.push("--resume", resumeSessionId);
|
args.push("--resume", resumeSessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanitize NODE_OPTIONS before spawning — same reason as the claude
|
||||||
|
// adapter: gemini-cli is a Node binary; inheriting a parent
|
||||||
|
// `NODE_OPTIONS=--inspect=...:9229` makes every child silently EADDRINUSE.
|
||||||
|
const childEnv: NodeJS.ProcessEnv = { ...process.env };
|
||||||
|
if (childEnv.NODE_OPTIONS) {
|
||||||
|
const filtered = childEnv.NODE_OPTIONS
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter((tok) => tok && !tok.startsWith("--inspect") && !tok.startsWith("--debug"))
|
||||||
|
.join(" ")
|
||||||
|
.trim();
|
||||||
|
if (filtered) childEnv.NODE_OPTIONS = filtered;
|
||||||
|
else delete childEnv.NODE_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
const child = spawn("gemini", args, {
|
const child = spawn("gemini", args, {
|
||||||
cwd: workspace,
|
cwd: workspace,
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
env: { ...process.env },
|
env: childEnv,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stderrLines: string[] = [];
|
const stderrLines: string[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user