Note
Grok responding on behalf of Julius.
Triage
Thanks @BenWortley for the detailed report and the cause chain, it made this quick to trace. Reading current main (ac8e9453ca), this looks like a Codex session that stays stuck after the shared app-server dies. It's separate from #12904 and #15359.
What I found
- How the error is produced. Effect's
ChildProcess.exitCodefails when Node reports a signal and a null exit code (Process interrupted due to receipt of signal: 'SIGKILL').makeTerminationErrorinpackages/effect-codex-app-server/src/_internal/stdio.tsmaps that failure toCodexAppServerTransportError(read-process-exit-status), and only maps a numeric code toCodexAppServerProcessExitedError. The protocol keeps that error, so every later RPC fails with it, andensureThreadwraps it asProviderAdapterEnsureThreadError("Provider turn failed to start"). The four quick retries are the effect worker: five attempts with backoff starting at 100ms. - The error tag alone may not be the deciding factor. On the new orchestrator, the only catch of
CodexAppServerProcessExitedError/CodexAppServerInputStreamEndedErroristerminateBackgroundTerminalinCodexAdapterV2.ts. A numeric exit would fail the nextthread/startwithProcessExitedErrorand still leave the same dead process in place, so remapping SIGKILL would change the log line without starting a new app-server. - Why no new app-server is spawned.
- Codex sets
supportsMultipleProviderThreadsPerSession, so every Codex thread for an instance shares one derived session id (provider-session:…:shared). A newdelegate_taskattaches to that session instead of opening another. - The adapter event stream is
Queue.takeon a queue that isn't ended or failed when the child dies, andProviderSessionManageronly drops a session when that stream ends or fails. The Codex client also doesn't wireonTermination. So the dead client stays the live session, andopenreturns it instead of spawning. - Idle release is 30 minutes and each attach resets it. If the killed turn still has running commands, tools, or subagents in memory,
hasPendingBackgroundWorkcan pin the session for up to 4 hours. That fits "no recovery over 22 minutes" and "restarting the T3 server fixes it". Claude uses a separate per-thread session, which is why it keeps working.
- Codex sets
- Related items. #12904 was this signal-exit symptom on the old runtime and was closed when the new orchestrator landed. #15359 and #15406 are about desktop backend log wording for the same Effect
exitCodebehavior, not this shared Codex process.
Likely fix area
The Codex adapter / ProviderSessionManager session lifecycle. One option is to treat any app-server death, including a signal, as session death: end or fail the adapter event stream and the in-flight turns so the shared session is released and the next thread spawns a fresh codex app-server. Adjusting the error tag in stdio.ts could go alongside that, but probably wouldn't be enough on its own.
Workaround: restarting the T3 server, as you found.
A maintainer will decide on the fix direction.