Summary
A --background Codex task can hang indefinitely when the turn's first model request stalls (e.g. a transient token-refresh / network stall in the detached, headless context). There is no turn-timeout or stall-heartbeat, so the turn stays parked at task_started forever, then the detached process eventually dies silently — producing no output and no error to the caller.
What we observed
A background delegation stalled and never recovered. The evidence:
-
Job log stops at the turn boundary and never advances:
Starting Codex Task.
Queued for background execution.
Starting Codex task thread.
Thread ready (<thread-id>).
Turn started (<turn-id>).
…and nothing after that.
-
Rollout JSONL for the thread contains only the opening events and then stops:
session_meta
event_msg: thread_settings_applied (model set, provider openai)
event_msg: task_started
No model response, no tool calls, no completion, no error event — the file simply stops growing.
-
The detached process is gone; the caller gets no result and no failure signal (it just waits).
It is not the model / auth / prompt
The same model + same prompt via foreground codex exec completes normally in ~40s. So the model, the ChatGPT-plan auth (token refreshes fine on-demand), and the prompt are all fine. The failure is specific to the background path lacking a timeout/recovery around the first model request.
Impact
Anyone delegating real work via --background can silently lose an entire task to a single transient stall, with no error to react to. A caller that just polls for completion waits forever.
Suggested fix
Add a turn-timeout or stall-heartbeat on background turns: if the rollout hasn't advanced past task_started (no new events) within N seconds, abort the turn with a visible error the caller can catch and retry — fail loudly and recoverably instead of hanging forever. A configurable timeout (e.g. background_turn_timeout_sec) would let callers tune it.
As a stopgap on the caller side, a stall can be detected externally by watching the rollout JSONL for growth: if the last event is still task_started / thread_settings_applied and the file hasn't grown for a few minutes, treat the task as dead and retry foreground. But a built-in timeout would be the right fix.
Environment
- codex-cli 0.144.1
- Claude Code Codex plugin, background (detached) task mode
- macOS
Summary
A
--backgroundCodex task can hang indefinitely when the turn's first model request stalls (e.g. a transient token-refresh / network stall in the detached, headless context). There is no turn-timeout or stall-heartbeat, so the turn stays parked attask_startedforever, then the detached process eventually dies silently — producing no output and no error to the caller.What we observed
A background delegation stalled and never recovered. The evidence:
Job log stops at the turn boundary and never advances:
…and nothing after that.
Rollout JSONL for the thread contains only the opening events and then stops:
No model response, no tool calls, no completion, no error event — the file simply stops growing.
The detached process is gone; the caller gets no result and no failure signal (it just waits).
It is not the model / auth / prompt
The same model + same prompt via foreground
codex execcompletes normally in ~40s. So the model, the ChatGPT-plan auth (token refreshes fine on-demand), and the prompt are all fine. The failure is specific to the background path lacking a timeout/recovery around the first model request.Impact
Anyone delegating real work via
--backgroundcan silently lose an entire task to a single transient stall, with no error to react to. A caller that just polls for completion waits forever.Suggested fix
Add a turn-timeout or stall-heartbeat on background turns: if the rollout hasn't advanced past
task_started(no new events) within N seconds, abort the turn with a visible error the caller can catch and retry — fail loudly and recoverably instead of hanging forever. A configurable timeout (e.g.background_turn_timeout_sec) would let callers tune it.As a stopgap on the caller side, a stall can be detected externally by watching the rollout JSONL for growth: if the last event is still
task_started/thread_settings_appliedand the file hasn't grown for a few minutes, treat the task as dead and retry foreground. But a built-in timeout would be the right fix.Environment