Summary
When /codex:adversarial-review --background (or /codex:rescue --background) is invoked from a Claude Code Agent subagent (e.g. Agent(subagent_type: "codex:codex-rescue")), the Codex job is terminated by the SessionEnd hook the moment the subagent's session ends — which is almost immediately, because the subagent's only task is to fire --background and return. Result: no notification arrives, job state shows running: [] and latestFinished: null, and output files remain 0 bytes.
Calling the same Codex --background command directly from the main Claude Code session (via Bash tool or top-level slash command) works as documented — push notification fires on completion.
Environment
- Claude Code: 2.1.91
- Codex Plugin: 1.0.4 (
codex@openai-codex)
- Codex CLI: 0.130.0
- Node.js: 24.13.1 (nvm-windows)
- OS: Windows Server 2025 (
sandbox = "elevated")
- Auth: ChatGPT Team
Steps to reproduce
In a Claude Code session running on Windows:
- Invoke the rescue subagent with a
--background task from the main session:
Agent(
subagent_type: "codex:codex-rescue",
prompt: "--background --task \"Review the current branch diff against main\""
)
- Observe the subagent returns immediately with an agentId and a "Codex is running in background" message.
- Wait 5–30 minutes (longer than any normal Codex review would take).
- Run
node scripts/codex-companion.mjs status --all --json.
Expected
status --all --json reports the job as running (or eventually completed), and on completion a push notification reaches the main Claude Code session.
Actual
{
"running": [],
"latestFinished": null,
"recent": [],
"needsReview": false
}
Job output file (<temp>/claude/.../tasks/<id>.output) stays at 0 bytes. No notification ever arrives. The job is gone.
Root cause (from reading the plugin source)
The Agent subagent has its own sessionId, distinct from the main session. The subagent fires Codex via Bash → codex-companion.mjs task --background, which tags the job with the subagent's sessionId and returns immediately. The subagent then exits, triggering SessionEnd hook for that subagent's session.
scripts/session-lifecycle-hook.mjs lines 41–74 (cleanupSessionJobs):
function cleanupSessionJobs(cwd, sessionId) {
// ...
const removedJobs = state.jobs.filter((job) => job.sessionId === sessionId);
// ...
for (const job of removedJobs) {
const stillRunning = job.status === "queued" || job.status === "running";
if (!stillRunning) continue;
try {
terminateProcessTree(job.pid ?? Number.NaN);
} catch { /* ignore teardown failures */ }
}
// ...
}
The newly-launched Codex job matches job.sessionId === sessionId (subagent's id) and is still queued/running, so terminateProcessTree(job.pid) kills the entire Codex process tree before Codex can write its completion state. From the main session's perspective, the job simply vanishes.
Why the same command works from the main session
When node codex-companion.mjs task --background is called directly from the main session (Bash tool, top-level slash command), the job is tagged with the main session's sessionId. The main session does not end during the Codex wait, so SessionEnd cleanup never fires. Codex runs to completion and pushes a notification normally.
Proposed fixes (any one, in order of preference)
-
Detect subagent context and refuse --background. If codex-companion.mjs task --background is invoked from a session that the plugin can identify as a transient Agent subagent (heuristic: very short session lifetime, or specific env var set by Claude Code for subagent invocations), error out with: --background is unsafe inside a subagent because SessionEnd will terminate the job. Use --wait instead. Lets users opt into Pattern B (Agent + --wait).
-
Re-tag the job with the parent session before forking. If a parent session ID is discoverable (e.g. via CLAUDE_PARENT_SESSION_ID env or similar), tag the job with the parent's id so SessionEnd of the subagent ignores it.
-
Document the limitation prominently in the codex:codex-rescue subagent prompt. Today the prompt says:
"If the request includes --background, run the codex:codex-rescue subagent in the background."
This is ambiguous — it does not warn that wrapping Codex --background inside an Agent subagent will cause the job to be killed by SessionEnd. Recommend rewording to clarify the correct execution pattern (either Bash from main session for fire-and-forget, or run_in_background=true on the Agent tool itself + Codex --wait inside).
Workaround for users
Until fixed, use one of:
Pattern A (recommended for fire-and-forget review):
Bash → node ~/.claude/plugins/cache/openai-codex/codex/<ver>/scripts/codex-companion.mjs task --background --task "..."
(Called from main session — no Agent wrapper. Notification will fire on completion.)
Pattern B (recommended for review whose result feeds back into main session work):
Agent(run_in_background=true, subagent_type: "codex:codex-rescue", prompt: "--wait --task ...")
(Codex --wait inside subagent → subagent waits → returns Codex result → harness notifies main session.)
Pattern C (BROKEN):
Agent(subagent_type: "codex:codex-rescue", prompt: "--background --task ...")
(SessionEnd kills Codex on subagent exit.)
References
- Plugin source:
scripts/session-lifecycle-hook.mjs:41-74 (cleanupSessionJobs)
- Plugin source:
scripts/lib/tracked-jobs.mjs:142-204 (job lifecycle)
- Architecture doc:
docs/architecture.md (hooks table)
- Empirical evidence: real session 2026-05-22, PR #358 Stage 4 review in
qtec-technology/axon (private repo, but the symptom is fully reproducible against any branch)
Happy to PR any of the proposed fixes (1, 2, or 3) if maintainers indicate a preference.
Summary
When
/codex:adversarial-review --background(or/codex:rescue --background) is invoked from a Claude Code Agent subagent (e.g.Agent(subagent_type: "codex:codex-rescue")), the Codex job is terminated by the SessionEnd hook the moment the subagent's session ends — which is almost immediately, because the subagent's only task is to fire--backgroundand return. Result: no notification arrives, job state showsrunning: []andlatestFinished: null, and output files remain 0 bytes.Calling the same Codex
--backgroundcommand directly from the main Claude Code session (via Bash tool or top-level slash command) works as documented — push notification fires on completion.Environment
codex@openai-codex)sandbox = "elevated")Steps to reproduce
In a Claude Code session running on Windows:
--backgroundtask from the main session:node scripts/codex-companion.mjs status --all --json.Expected
status --all --jsonreports the job asrunning(or eventuallycompleted), and on completion a push notification reaches the main Claude Code session.Actual
{ "running": [], "latestFinished": null, "recent": [], "needsReview": false }Job output file (
<temp>/claude/.../tasks/<id>.output) stays at 0 bytes. No notification ever arrives. The job is gone.Root cause (from reading the plugin source)
The Agent subagent has its own
sessionId, distinct from the main session. The subagent fires Codex via Bash →codex-companion.mjs task --background, which tags the job with the subagent'ssessionIdand returns immediately. The subagent then exits, triggeringSessionEndhook for that subagent's session.scripts/session-lifecycle-hook.mjslines 41–74 (cleanupSessionJobs):The newly-launched Codex job matches
job.sessionId === sessionId(subagent's id) and is stillqueued/running, soterminateProcessTree(job.pid)kills the entire Codex process tree before Codex can write its completion state. From the main session's perspective, the job simply vanishes.Why the same command works from the main session
When
node codex-companion.mjs task --backgroundis called directly from the main session (Bash tool, top-level slash command), the job is tagged with the main session'ssessionId. The main session does not end during the Codex wait, soSessionEndcleanup never fires. Codex runs to completion and pushes a notification normally.Proposed fixes (any one, in order of preference)
Detect subagent context and refuse
--background. Ifcodex-companion.mjs task --backgroundis invoked from a session that the plugin can identify as a transient Agent subagent (heuristic: very short session lifetime, or specific env var set by Claude Code for subagent invocations), error out with:--background is unsafe inside a subagent because SessionEnd will terminate the job. Use --wait instead.Lets users opt into Pattern B (Agent +--wait).Re-tag the job with the parent session before forking. If a parent session ID is discoverable (e.g. via
CLAUDE_PARENT_SESSION_IDenv or similar), tag the job with the parent's id so SessionEnd of the subagent ignores it.Document the limitation prominently in the
codex:codex-rescuesubagent prompt. Today the prompt says:This is ambiguous — it does not warn that wrapping Codex
--backgroundinside an Agent subagent will cause the job to be killed by SessionEnd. Recommend rewording to clarify the correct execution pattern (either Bash from main session for fire-and-forget, orrun_in_background=trueon the Agent tool itself + Codex--waitinside).Workaround for users
Until fixed, use one of:
Pattern A (recommended for fire-and-forget review):
(Called from main session — no Agent wrapper. Notification will fire on completion.)
Pattern B (recommended for review whose result feeds back into main session work):
(Codex
--waitinside subagent → subagent waits → returns Codex result → harness notifies main session.)Pattern C (BROKEN):
(SessionEnd kills Codex on subagent exit.)
References
scripts/session-lifecycle-hook.mjs:41-74(cleanupSessionJobs)scripts/lib/tracked-jobs.mjs:142-204(job lifecycle)docs/architecture.md(hooks table)qtec-technology/axon(private repo, but the symptom is fully reproducible against any branch)Happy to PR any of the proposed fixes (1, 2, or 3) if maintainers indicate a preference.