Environment
- Plugin:
codex@1.0.6 (marketplace openai/codex-plugin-cc, commit db52e28)
- Claude Code CLI 2.1.238 on macOS Darwin 25.5.0
- Rescue subagent model: sonnet
- Note: the session ran with user-level config (CLAUDE.md, custom hooks). I have not yet reproduced end-to-end in a clean profile, so this report presents an observed occurrence plus an isolated reproduction of the persistence mechanism — not a deterministic repro.
Symptom
After several /codex:rescue --background ... delegations, the session accumulates background tasks that never finish. Their process shape is always:
zsh -c while pgrep -f "codex-companion.mjs" > /dev/null 2>&1; do sleep 5; done; echo "===PROCESS_DONE==="; cat "<claude tasks dir>/<id>.output"
They stay alive after the Codex job (and the companion process) has completed, each showing as a stuck "background task running" entry in the session UI. Five instances observed over two days of normal use.
Observed occurrence (timestamped, from the subagent transcript, 2026-08-25)
| Time (UTC) |
Event |
| 11:09:57 |
subagent runs codex-companion.mjs task ... (foreground Bash) |
| 11:20:00 |
harness: "Command did not complete within its 600s timeout and was moved to the background (ID: bankuk08v)" |
| 11:20:49 |
subagent issues wait loop #1 (while pgrep -f "codex-companion.mjs" ...; cat .../bankuk08v.output) |
| 12:07:08 |
(later round) wait loop targeting .../bcjiv3vzi.output issued |
| 12:16:59 |
harness: that wait loop itself "moved to the background (ID: bajyfm7mc)" — still running at this moment |
| 12:17:01 |
subagent issues a second identical wait loop for the same output file — two seconds later, i.e. within a single sleep 5 iteration of the first |
| 12:26:52 |
harness: second loop also "moved to the background (ID: b5wz9tl0t)" |
Six such auto-backgrounded wait loops accumulated in this one session (IDs bajyfm7mc, b5wz9tl0t, bb662rqqw, by7r8ttif, b322f0nm6, byx7cqzqh), and the stuck-task symptom above matches sustained overlap.
Where the loop comes from
The plugin source at db52e28 contains no pgrep, so the command is model-generated. agents/codex-rescue.md requires the subagent to use exactly one Bash call, return the companion stdout as-is, and forbids polling status / fetching result. Once the companion call is auto-backgrounded at the Bash timeout, there is no compliant way left to "return the stdout as-is", and the model recovers by writing a pgrep wait loop — noncompliant with the letter of the contract, but a predictable failure mode of it: the prompt defines no fallback for the timeout/auto-background case, and the same improvisation recurred across independent runs (8+ wait loops in the transcript above).
Why the loops never exit (isolated mechanism reproduction)
The pgrep pattern codex-companion.mjs appears verbatim inside each wait loop's own zsh -c command line. Verified experimentally on the tested Darwin 25.5 setup:
- A single loop did not match its own ancestor shell and exited once no companion process remained. This matches the documented BSD pgrep default: "the current pgrep or pkill process and all of its ancestors are excluded" (macOS
man pgrep, -a flag description).
- Two sibling loops matched each other and stayed alive indefinitely after every codex-related process was gone (observed over several minutes; each loop's command line satisfies the other's pgrep).
pgrep -f was confirmed to match the full zsh -c argv at least to offset 2000, so the loop body is always visible to a sibling.
This establishes the persistence mechanism whenever two waiters overlap — and the timeline above shows the plugin flow producing exactly that overlap. It does not claim process-name waiting is generally safe in the single-loop case.
Untested expectation (not required for this report): on procps-based Linux, pgrep excludes only itself, so even a single loop is expected to match its own parent shell and never exit.
Potential fixes
- Define an explicit timeout/background fallback in the agent contract (
agents/codex-rescue.md, skills/codex-cli-runtime/SKILL.md): if the Bash call times out or is moved to the background, return the job handle / harness message immediately; never issue another Bash call and never construct a waiter.
- Provide a job-specific blocking await and let the subagent use it (e.g.
codex-companion.mjs status <job-id> --wait --timeout-ms ...). Today the "do not call status" rule closes off the safe path while the missing stdout creates the pressure.
- Supervise completion inside the companion:
task --background self-daemonizes and writes its result to a known file, so no shell-side waiting is needed.
(The bracket pattern pgrep -f "[c]odex-companion.mjs" prevents waiter self/mutual matching, but it is globally scoped — it can wait on unrelated concurrent companion jobs — so it is only a narrow mitigation, not a fix.)
Related
Happy to provide fuller transcript excerpts (with timestamps and task IDs) or run additional experiments if that helps.
Environment
codex@1.0.6(marketplaceopenai/codex-plugin-cc, commitdb52e28)Symptom
After several
/codex:rescue --background ...delegations, the session accumulates background tasks that never finish. Their process shape is always:They stay alive after the Codex job (and the companion process) has completed, each showing as a stuck "background task running" entry in the session UI. Five instances observed over two days of normal use.
Observed occurrence (timestamped, from the subagent transcript, 2026-08-25)
codex-companion.mjs task ...(foreground Bash)while pgrep -f "codex-companion.mjs" ...; cat .../bankuk08v.output).../bcjiv3vzi.outputissuedsleep 5iteration of the firstSix such auto-backgrounded wait loops accumulated in this one session (IDs bajyfm7mc, b5wz9tl0t, bb662rqqw, by7r8ttif, b322f0nm6, byx7cqzqh), and the stuck-task symptom above matches sustained overlap.
Where the loop comes from
The plugin source at
db52e28contains nopgrep, so the command is model-generated.agents/codex-rescue.mdrequires the subagent to use exactly one Bash call, return the companion stdout as-is, and forbids pollingstatus/ fetchingresult. Once the companion call is auto-backgrounded at the Bash timeout, there is no compliant way left to "return the stdout as-is", and the model recovers by writing a pgrep wait loop — noncompliant with the letter of the contract, but a predictable failure mode of it: the prompt defines no fallback for the timeout/auto-background case, and the same improvisation recurred across independent runs (8+ wait loops in the transcript above).Why the loops never exit (isolated mechanism reproduction)
The pgrep pattern
codex-companion.mjsappears verbatim inside each wait loop's ownzsh -ccommand line. Verified experimentally on the tested Darwin 25.5 setup:man pgrep,-aflag description).pgrep -fwas confirmed to match the fullzsh -cargv at least to offset 2000, so the loop body is always visible to a sibling.This establishes the persistence mechanism whenever two waiters overlap — and the timeline above shows the plugin flow producing exactly that overlap. It does not claim process-name waiting is generally safe in the single-loop case.
Untested expectation (not required for this report): on procps-based Linux, pgrep excludes only itself, so even a single loop is expected to match its own parent shell and never exit.
Potential fixes
agents/codex-rescue.md,skills/codex-cli-runtime/SKILL.md): if the Bash call times out or is moved to the background, return the job handle / harness message immediately; never issue another Bash call and never construct a waiter.codex-companion.mjs status <job-id> --wait --timeout-ms ...). Today the "do not callstatus" rule closes off the safe path while the missing stdout creates the pressure.task --backgroundself-daemonizes and writes its result to a known file, so no shell-side waiting is needed.(The bracket pattern
pgrep -f "[c]odex-companion.mjs"prevents waiter self/mutual matching, but it is globally scoped — it can wait on unrelated concurrent companion jobs — so it is only a narrow mitigation, not a fix.)Related
Happy to provide fuller transcript excerpts (with timestamps and task IDs) or run additional experiments if that helps.