SIGN IN SIGN UP

fix(byoa): tear down a Codex session when the thread handshake fails (#24)

CodexSession's app-server handshake is one-shot: `threadReq` is consumed at the
`initialize` ack, and only a failed `thread/resume` ever re-issues a
`thread/start`. So once the thread fails to open, `ready` can never flip again.

`failPending()` settles the in-flight turn but leaves the process running — and
the app-server SURVIVES rejecting a handshake (a malformed ~/.codex/config.toml,
a model the account can't use, protocol drift after a codex upgrade). Since
`alive` only checks `!exited && stdin.writable`, the session keeps advertising
itself as usable, so the daemon reuses it on every later wake. `send()` then
takes the `else this.queuedPrompt = prompt` branch, and with nothing left that
can call `onThreadReady()`, that promise never resolves.

The daemon awaits it with no timeout, so `busy` never clears, the big-brain
semaphore slot is never released, and `beatRun()` keeps heartbeating the run row
so the server's stale-run sweeper never flags it either. The user sees one
"could not run on local codex" notice from turn 1 and then an agent that is
silently and permanently dead until the daemon is restarted.
CUMORA_TURN_TIMEOUT_MS, documented as the cap on a single persistent-engine
turn, is wired only into ClaudeSession — CodexSession has no timer at all.

Treat a failure that happens before the thread ever opened as fatal to the
SESSION, not just the turn: remember the reason and stop the process. The
daemon's existing `if (!session.alive) this.engineSession = null` then drops the
zombie and the next wake spawns a clean session.

Turn 1's result is unchanged (exitCode 1 plus the engine's real error text);
turn 2 now settles immediately with that same real reason instead of hanging.
Post-handshake failures are untouched — a turn/start error, an `error`
notification, or `turn/completed` with status failed still fail only that turn
and keep the session alive, because `ready` is true by then.
X
Xialie Zhuang committed
81e81def952fd2d66c05d215da2e1991148a8f97
Parent: 97f77ce
Committed by GitHub <noreply@github.com> on 8/19/2026, 8:00:16 PM