Repository navigation
Description
Symptom
When a parent agent calls subagent_type: codex:codex-rescue expecting a substantive answer, the wrapper returns within seconds with text like:
"Codex is running the review in the background. You will be notified when the output is ready."
The actual task spawned by the wrapper continues in a separate background job; the parent agent never gets the real result because it was subscribed to the wrapper agent (which exited immediately), not to the orphaned background job that holds the real Codex output.
Discovered while running a multi-reviewer cycle against a third-party plugin: the Codex rescue stub returned within seconds while the real task-worker continued for ~18 minutes and died without producing a verdict.
Reproduction
From a parent agent (Claude Code session):
Agent({
subagent_type: "codex:codex-rescue",
prompt: "Review <some code>... return a structured verdict."
})
Expected: the wrapper waits for Codex, returns the verdict.
Observed: the wrapper returns within seconds with "Codex is running the review in the background."
Root cause (3 stacked issues)
Issue 1: task subcommand is async-by-design
In scripts/codex-companion.mjs:643:
const child = spawn(process.execPath, [scriptPath, "task-worker", "--cwd", cwd, "--job-id", jobId], {
...
detached: true,task spawns a detached task-worker and returns a job ID. Without --wait, the call returns immediately and the actual reasoning happens in the spawned worker.
Issue 2: rescue skill instructions STRIP --wait
skills/codex-cli-runtime/SKILL.md says:
If the forwarded request includes
--backgroundor--wait, treat that as Claude-side execution control only. Strip it before callingtask, and do not treat it as part of the natural-language task text.
So even if the parent passes --wait, the rescue wrapper removes it. The wrapper never adds --wait either — the task call always runs in detached/async mode.
Issue 3: Claude Code's Bash tool further auto-backgrounds long commands
When the wrapper agent's Claude calls Bash(node codex-companion.mjs task "..."), the Bash tool itself auto-decides to background the command. The wrapper sees only the Bash auto-background acknowledgement:
"Command running in background with ID: bodpz7pmd. Output is being written to: /private/tmp/claude-501/.../tasks/bodpz7pmd.output"
…and forwards that to its parent verbatim, per the rescue skill's "return stdout exactly as-is" rule.
Real Codex output goes to bodpz7pmd.output, which the parent never knows about.
Evidence from a real session
Auto-background output landed at:
/private/tmp/claude-501/.../tasks/bodpz7pmd.output
23 lines of genuine Codex progress (excerpts):
[codex] Starting Codex task thread.
[codex] Thread ready (019e2b02-ded1-7e20-a0f3-b584b6b6b3e7).
[codex] Turn started.
[codex] Assistant message captured: I'll try the requested checkout first.
[codex] Searching:
[codex] Assistant message captured: The direct clone is blocked by the read-only sandbox: `/tmp/grok-v060-codex` cannot be created
[codex] Calling codex_apps/github_fetch_file.
[codex] Tool codex_apps/github_fetch_file completed.
...
Codex was actually working — hit a read-only sandbox blocking git clone, pivoted to GitHub API fetches, ran for ~18 minutes, then died without producing a final verdict. The parent agent had no visibility into any of this.
Suggested fixes (any one is sufficient)
Fix A (cleanest) — drop the strip---wait rule from the skill
In skills/codex-cli-runtime/SKILL.md, change the execution rule from:
If the forwarded request includes
--backgroundor--wait, treat that as Claude-side execution control only. Strip it before callingtask.
to:
Always pass
--waittotaskfrom inside the rescue subagent — the rescue contract is "block until done and return the final result." If the user also passed--background, strip it (rescue is never async).
This makes the wrapper synchronous from the parent's point of view, which is what the rescue subagent contract implies.
Fix B — task defaults to synchronous when invoked from a rescue context
Detect inside codex-companion.mjs::handleTask that the caller is non-interactive (no TTY, or process.env.CODEX_RESCUE_MODE=1) and skip the detached spawn — just await the worker inline.
Fix C — wrapper polls until done
The skill instructs the wrapper to call task → capture job ID → loop on status --wait <id> until terminal status → return final result. More changes to the skill, but does not require changes to task.
Fix A is the smallest delta and matches what users actually expect from a rescue subagent.
Related
I found the same pattern in taibaran/gemini-plugin-cc (same async-task + strip---wait design). Opening a parallel issue there.
Cross-link: LovelaceLoom/gemini-plugin-cc#3
Reported while building taibaran/grok-plugin-cc, a third Claude-Code plugin modeled on this one — context for why the multi-reviewer flow surfaced the bug.
Activity
v0.7.1: aggregate-review routes through peer plugin companions
fix(rescue)!: unify --background / --wait policy + drop auto-promote …
docs(rescue): formalize 600 s foreground ceiling + background+poll wo…
gitctor commentedon May 27, 2026
Confirming this still repros on plugin 1.0.4 — independent observation 2026-05-27, macOS, Claude Code CLI. Posting both as a "+1 with data" and as a 4th fix option that's narrower than A/B/C.
Independent repro
Parent Claude in a long-running session spawned
codex:codex-rescuefor an architecture review of a Minecraft modpack repo (~9 minutes of codex work, write-mode disabled). The subagent'sBashcall tocodex-companion.mjs task ...did not passtimeout, so the harness default (120000ms foreground) applied. At the 2-minute mark the harness auto-promoted the Bash call to background mode and returned a synthetic "Command running in background with ID: …" line.The subagent emitted text equivalent to "I'll be notified when it finishes" and exited. Codex itself completed cleanly ~7 minutes later — the synthesis is preserved in the task
.outputfile and in~/.codex/sessions/2026/05/27/rollout-*.jsonl, but the parent Claude never saw it. From the parent's perspective: rescue returned a stub, no error, and the actual verdict was orphaned.This matches Issue 3 in the original report exactly. The only difference vs your repro is that I didn't pass
--backgroundanywhere — the strip---waitrule means foreground is the contract, but the harness still backgrounds the Bash call once it crosses 2 minutes. So this bug fires on every codex-rescue invocation that takes longer than 2 minutes, regardless of what flags the parent passes.Fix D — agent-side
timeouton the Bash callThe three fixes you listed change the companion (B), the skill (A), or add polling logic (C). There's a smaller delta that doesn't touch the companion or the skill: require the agent to pass
timeout: 600000(10 min, Claude Code's Bash max) on the call itself.In
plugins/codex/agents/codex-rescue.md, the forwarding rules become:(Replaces the current "prefer background execution if the task looks complicated, open-ended, multi-step, or likely to keep Codex running for a long time" heuristic at
agents/codex-rescue.md:24, which interacts badly with the harness auto-promotion.)Why this might be the right call
taskkeeps its current async-by-design semantics. The--waitflag and the strip---waitskill rule stay as-is.--backgroundand get a job ID — which is the codex-companion mode that was already designed for this case but never reachable through the rescue agent.Where Fix D doesn't help
It doesn't help if a single rescue genuinely runs past 10 minutes without the user knowing in advance. For that case, A or B is still needed. But for the orphan-task class described in this issue, D is the smallest fix.
Combination
Fix D + the fix from #350 (forward stderr + exit code on Bash failure) closes both the silent-stub path (this issue) and the silent-empty-output path (#350) without touching the companion. Both are agent-prompt-only changes.
What's deployed locally as a workaround
I shipped a local override at
~/.claude/agents/codex-rescue.mdthat takes precedence over the plugin's copy and does the three changes above. Pattern has held for the rest of the day across 4 more rescues, all of which surfaced full codex output to the parent. Happy to PR the same as a plugin change if a maintainer signals which of A/B/C/D you'd prefer to land.docs(exploration): Phase B — suminerProxy fork eval + upstream issue …
mychaelconnolly commentedon Jun 27, 2026
Reproced this one on macOS (darwin 25.5), plugin 1.0.4, codex-cli 0.142.2 — with an extra symptom worth noting.
Parent
Agent({subagent_type: "codex:codex-rescue"})returned the "running in background, will notify" stub; the realtask-workerran ~25 min and died without a verdict (as described here). Additionally: because the worker roots at the session cwd (resolveCommandCwd→process.cwd(), per #380), and our target repo was not the session cwd, the orphaned worker reviewed the wrong tree — its transcript cited files that don't exist in the target repo (confabulated paths).So the stub-then-die failure (#324) compounds with the cwd default (#380): the run looks like a stall, and any partial output is ungrounded. A loud "task-worker exited without result" + honoring a per-task target cwd would both help.
ElliotDrel commentedon Jul 14, 2026
Adding a data point from the other failure mode of the same wrapper (cross-ref #370).
Same root cause you describe (the rescue wrapper's
taskpath + Bash auto-backgrounding), but the observable symptom here was total output loss rather than a stub:subagent_type: codex:codex-rescuefrom a parent Claude Code session with an adversarial multi-file review prompt.Bashcall ran until Claude Code's 10-min ceiling and was killed; the parent received "The Bash call timed out after 10 minutes" and nothing else — no stub, no job id, no.outputpath to recover from.So between this issue and #370, the
codex-rescuewrapper can fail in two ways depending on whether Bash backgrounds the call: (a) return a stub pointing at an orphaned job the parent never subscribed to (#324), or (b) return nothing at all when the foreground call is killed at 10:00 (#370). Both leave the parent with no usable Codex output. A fix that makes the wrapper always surface the real job id + output path (even on timeout/stub) would cover both.Environment: Windows 11, Claude Code, codex plugin 1.0.6, Codex CLI 0.144.3.
Captain-Bacon commentedon Aug 31, 2026
Same symptom, and two compounding notes worth adding
v 1.0.6
Claude Code on macOS (darwin), Node 22
1. Chasing the missing result made it worse. When the main agent gets the "running in the background" stub and messages the rescue subagent to resume/collect, each nudge spawns a fresh job whose log contains only
Starting Codex Resume.in the /state//jobs/ folder, while the original job keeps running untouched. In my case the chase ended with the original, still-running review being cancelled by cleanup - the full round-trip cost on just that one instance was one lost ~15-minute review plus a re-run.2. It overlaps with #639. While the original job is still
running,result <jobId>throwsNo job found for "<jobId>"(the finished-only filter, per that issue), so the natural recovery path also dead-ends. The main agent can see the job instatus, but neither the wrapper norresultwill hand over anything until it finishes. Nothing tells the wrapper to wait.Workaround
Patched the local
skills/codex-cli-runtime/SKILL.mdto add:With that in place (or
--waitwritten into the dispatch prompt), every rescue since has returned the actual Codex output. Since the cache is versioned, the patch will be overwritten on next version update, so a fix in the shipped skill (or defaultingtaskto foreground when invoked from the rescue skill, per #395's proposal) would be very welcome.germanelias13 commentedon Nov 4, 2026
A data point on Issue 3 above, from plugin 1.0.6 and Claude Code 2.1.317 on Windows 11 (2026-11-04).
In two runs the agent ran
task --effort medium --prompt-file <path>in the foreground withtimeout: 600000. At about 180 seconds the Bash tool still moved the call to the background, so a long timeout doesn't prevent this here (#634 reports 120 seconds on Linux). The agent then handed back a report with no result. The companion was not killed: it ran to the end, and a second report with Codex's answer followed. When to expect it depends on the caller. In a third run the caller was busy with other tool calls, and the second report came only when the caller's own turn ended, 21 minutes after the job had finished, though the answer had been in the Bash output file all along. In a fourth run the caller was idle, and it came 19 seconds after the job finished.So the first report is not a failure, but nothing says so.
agents/codex-rescue.mdsays to return the stdout as-is (line 41) and to return nothing if the call fails (line 42), and neither fits a call that is still running. A caller told to carry on if Codex fails can read that report as a failure, then skip the step or start a second run.Suggested rule for the agent: when the call moves to the background, say that the job is still running, that this report is not the result, that a final report will follow, and where the output is in the meantime. Then do nothing else. This doesn't replace the fixes discussed above for getting the result back. It only keeps the caller from misreading the first report.
smontoy1-vasu commentedon Nov 4, 2026
Second sighting, twice in one session. codex plugin 1.0.6, codex-cli 0.166.0, Claude Code 2.1.316, macOS 26.6.2 (arm64). Both times the codex:codex-rescue subagent, launched in the background with an xhigh read-only review prompt, returned after about 2-3 minutes with only the [codex] progress lines and no final answer, while the companion job kept running. Reading the result needed
codex-companion.mjs statusandresultby hand. Workaround that worked: running the same prompt throughcodex exec -s read-only -o <file>directly.