What happened
A wedged Claude CLI child can crash the entire gateway with process.exit(1).
In the claude-cli live session, the turn's outputPromise is created at src/agents/cli-runner/claude-live-session.ts:1365 but no rejection handler is attached until after await writeTurnInput(...) at :1392. writeTurnInput only settles when the child's stdin write callback fires (:1038-1052). If the child process is alive but not reading stdin — hung at startup on auth/network, or a first-turn reseed history prompt larger than the 64 KiB pipe buffer — the write stays pending. When the turn's no-output timer fires (:1184-1204), failTurn rejects outputPromise while it has zero handlers. Node emits unhandledRejection; src/infra/unhandled-rejections.ts:527-561 does not recognize FailoverError as suppressible and calls process.exit(1).
One stuck CLI turn takes down every agent and every channel on the gateway.
Why tests never caught it
Mock stdin in cli-runner.spawn.test.ts:3053-3067 invokes the write callback synchronously, so writeTurnInput always settles before the timers can fire.
Expected behavior
A turn timeout while the prompt write is pending should fail that turn via the normal failover path, never crash the gateway.
Environment
Found by code audit of src/agents/cli-runner/ on current main; mechanism is deterministic given a pending stdin write plus timer expiry.
What happened
A wedged Claude CLI child can crash the entire gateway with
process.exit(1).In the claude-cli live session, the turn's
outputPromiseis created atsrc/agents/cli-runner/claude-live-session.ts:1365but no rejection handler is attached until afterawait writeTurnInput(...)at:1392.writeTurnInputonly settles when the child's stdin write callback fires (:1038-1052). If the child process is alive but not reading stdin — hung at startup on auth/network, or a first-turn reseed history prompt larger than the 64 KiB pipe buffer — the write stays pending. When the turn's no-output timer fires (:1184-1204),failTurnrejectsoutputPromisewhile it has zero handlers. Node emitsunhandledRejection;src/infra/unhandled-rejections.ts:527-561does not recognizeFailoverErroras suppressible and callsprocess.exit(1).One stuck CLI turn takes down every agent and every channel on the gateway.
Why tests never caught it
Mock stdin in
cli-runner.spawn.test.ts:3053-3067invokes the write callback synchronously, sowriteTurnInputalways settles before the timers can fire.Expected behavior
A turn timeout while the prompt write is pending should fail that turn via the normal failover path, never crash the gateway.
Environment
Found by code audit of
src/agents/cli-runner/on currentmain; mechanism is deterministic given a pending stdin write plus timer expiry.