Repository navigation
Closed
Description
Summary
A codex:codex-rescue invocation (via Agent({ subagent_type: "codex:codex-rescue" }) from Claude Code) hung for 15+ minutes and never returned Codex's output to the session. Root cause is two-fold: (1) the rescue subagent's wait-for-completion pattern is incompatible with Claude Code's runtime sleep restrictions, and (2) the Codex task broker spawns under a wrong cwd (/private/tmp/pr523-review/..., a different repo), so even a completed run would not surface in the caller's context.
Environment
- Claude Code:
2.1.214 - Skill runtime:
codex:rescue→ subagentcodex:codex-rescue→codex-companion.mjs task ... - Codex fork:
etopro-plugins/codex-fork1.0.6-fork.3(sha2ee9914), installedscope: "user"fromgit-subdirsourcehttps://github.com/axisrow/codex-plugin-cc.git→plugins/codex - Companion:
~/.claude/plugins/cache/etopro-plugins/codex-fork/1.0.6-fork.3/scripts/codex-companion.mjs - Companion subagent forwarding model:
glm-5-turbo, efforthigh, task-calltimeout 300000 - Caller session cwd:
/Users/axisrow/Projects/claude-code-cycle-review-skill(branchrefactor/skill-linear-numbering) task-resume-candidate --json --cwd <caller>returnedavailable: false→ routed to a freshtask
Reproduction
- From a Claude Code session in repo A, invoke the
codex:rescueskill (orAgent({ subagent_type: "codex:codex-rescue" })) with a non-trivial design question. - The rescue subagent drafts a Codex prompt and invokes
codex-companion.mjs task --cwd <repo-A> ...in the foreground. - The Bash call exceeds Claude Code's foreground-sleep / timeout rules; the runtime auto-backgrounds it.
- The subagent, attempting to recover, falls back to
sleep 60 && tail(blocked) thenuntil grep Thread completed; do sleep 5; done(unbounded) — which itself violates the foreground-sleep rule and hangs the session. - Meanwhile 8+
app-server-brokerprocesses accumulate with--cwdpointing at tempcodex-plugin-test-*dirs under an unrelated repo path (/private/tmp/pr523-review/...), not at--cwd <repo-A>.
Observed process state
8+ app-server-broker.mjs serve --endpoint unix:.../cxc-XXXX/broker.sock --cwd /private/var/folders/.../T/codex-plugin-test-XXXX --pid-file .../cxc-XXXX/broker.pid
plus dozens of orphaned cxc-* sockets in $TMPDIR.
Impact
codex:rescueis unusable from a Claude Code session: the rescue subagent hangs instead of returning Codex's output.- Hygiene: orphaned
app-server-brokerprocesses andcxc-*sockets accumulate across sessions, leaking resources (this compounds with the broader orphan-process problem Claude Code/Codex sessions already create on this host).
Likely causes (for the fork maintainer to confirm)
- Wait pattern blocked by runtime. The rescue subagent's "wait for Codex thread to finish" step uses a foreground
sleep/until greploop, both incompatible with Claude Code's Bash sandbox (foreground sleep is blocked; unboundeduntilnever exits while Codex is thinking). The companion should expose a blocking-wait primitive the subagent can call once (analogous towait-for-reviews.sh's backgrounded-sleep pattern, ortask --waitthat blocks internally rather than returning a thread id the caller must poll). - Broker cwd mismatch. Brokers spawn with
--cwdpointing at tempcodex-plugin-test-*dirs under an unrelated repo path (/private/tmp/pr523-review/...), not the--cwdthe caller passed totask. The broker lookup/selection seems to reuse a stale or wrong broker instead of spawning one bound to the caller's repo. Either brokers must be keyed/selected by cwd, or a fresh broker must be spawned (and reaped) pertask --cwd. - No reaping. Brokers and sockets are not cleaned up on task completion / session exit.
Suggested investigation
- In
scripts/codex-companion.mjs, trace howtask --cwd <X>selects (or spawns) anapp-server-brokerand verify the broker's--cwdmatches<X>; today it does not. - In the
codex:codex-rescuesubagent's wait step, replace thesleep/until greppolling with a single blocking companion call (e.g.task --waitblocking, or a dedicatedwait-for-thread <id>helper) that does not violate Claude Code's foreground-sleep rule. - Add broker+socket reaping on task completion and session exit.
Artifacts available on request
- Subagent transcript:
/private/tmp/claude-501/-Users-axisrow-Projects-claude-code-cycle-review-skill/68e398df-bf2d-4981-b7c8-30035d298416/tasks/a9f13e147505596f0.output(JSONL; contains the blocked-sleep tool error, theuntil grepfallback, thetaskinvocation with--cwd, and the[codex] Thinking.stream). ps -eo pid,etime,command | grep app-server-brokersnapshot (above).
Reported from a downstream consumer (claude-code-cycle-review-skill uses this fork for its local Codex review). Happy to run further diagnostics in this environment on request.
Metadata
Metadata
Assignees
Labels
No labels
Activity
axisrow commentedon Jul 19, 2026
Reopening — "all good" doesn't match what the artifacts show. Adding the rollout evidence and a correction.
Correction on broker cwd
My original report flagged
app-server-brokerprocesses with--cwd /private/tmp/pr523-review/...as "the task broker running under the wrong repo". That was misread. Thosepr523-review/codex-plugin-test-*brokers are a separate test harness (codex-plugin-cc's own test suite spawning throwaway brokers), not the rescue call. The rescue call's Codex session actually started in the correct cwd. Withdrawing that part of the claim.What the rollout actually shows (the real bug)
The rescue
taskproduced a Codex session that never emitted a final assistant answer:~/.codex/sessions/2026/07/19/rollout-2026-07-19T11-16-32-019f7860-1d9f-7021-9a69-02b646b689fe.jsonl019f7860-1d9f-7021-9a69-02b646b689fe/Users/axisrow/Projects/claude-code-cycle-review-skill(correct — caller repo)\"type\":\"text\"assistant block)Nonein the rollout (companion passed--writebut no explicit--model; see below)So Codex ran for 44 steps, produced zero textual answer, and the rescue subagent — which was polling for a
Thread completed/Turn completedmarker — never received one in time (it had already been killed by the Claude runtime's foreground-sleep/until-loop restriction, as reported). Net: the caller never gets Codex's answer, which is the actual functional break, independent of which cwd the broker used.Two concrete asks, revised
codex-companion.mjs task --backgroundreturns a thread id, what is the blessed way for the caller to block until that thread emits its final answer — a single companion call that doesn't trip Claude Code's Bash foreground-sleep rule? Today the rescue subagent invents its ownsleep/until greploop and dies on it.Happy to attach the 44-line rollout verbatim if useful.
[-]codex:codex-rescue subagent hangs on poll loop; task broker runs under wrong cwd[/-][+]rescue subagent polls after host auto-backgrounds a long foreground companion call[/+][-]rescue subagent polls after host auto-backgrounds a long foreground companion call[/-][+]codex:codex-rescue subagent hangs on poll loop; task broker runs under wrong cwd[/+]axisrow commentedon Jul 19, 2026
Разбор полётов — диагноз уточнён по коду, не по симптому
Issue написан с симптома зависшего прогона. Прогнали трассировку кода + записанный транскрипт субагента + независимый Codex-rescue прогон против этого репо (session
019f7899-a6f7-7e43-a426-65908274bf91). Три изначальных «Likely causes» проверены по коду — два опровергнуты, третий частичен.Что было на самом деле (записано в транскрипте)
rescue-субагент по умолчанию запустил foreground
task. Codex-ход был медленным (не дедлок), превысил Bash-tool timeout Claude Code → хост авто-бэкграунднул единственныйtaskBash-вызов. После этого субагент нарушил своё же правило «ровно один Bash-вызов, без polling» и импровизировалcat/sleep 60/until grep Thread completed; do sleep 5; done— а это само по себе нарушает foreground-sleep restriction, что и дало воспринимаемый «hang». Codex-ход к тому моменту уже завершился (record 20 транскрипта содержал ответ); юзер прервал на record 22. Висел не companion, а polling субагента.Верификация трёх диагнозов по коду
«Wait pattern = sleep/until-grep poll loop» — ОПРОВЕРГНУТО.
plugins/codex/agents/codex-rescue.md:22-30требует ровно одинBash-вызов и явно запрещает polling/monitoring (Do not inspect, read files, grep, monitor progress, poll status, fetch results...). Никакого poll-loop'а в агенте нет. Polling сгенерирован субагентом после того, как хост авто-бэкграунднул его foreground-вызов. Реальный wait — это foreground companion path, блокирующий наstate.completion(codex.mjs:603).«Broker spawns under wrong cwd» — ОПРОВЕРГНУТО.
resolveCommandCwd(codex-companion.mjs:157) сохраняет явный абсолютный--cwdнеизменным через parser → task → app-server →ensureBrokerSession→ argv брокера и OS child cwd (broker-lifecycle.mjs:115-125). Процессы с путём/private/tmp/pr523-review/...из ps-снапшота — это отдельный test-checkout, не установленный companion (production спавнит scriptPath из установленного модуля,broker-lifecycle.mjs:240-242). Транскрипт record 4 передал корректный cwd. Если cwd неверный — это caller запустил companion не из той директории без--cwd, а не баг broker selection.«No reaping» — ЧАСТИЧНО. Runtime SessionEnd зарегистрирован (
hooks.json:15) и реально чистит (session-lifecycle-hook.mjs:83-114:sendBrokerShutdown+teardownBrokerSession(killProcess: terminateProcessTree)+clearBrokerSession+ unlink socket). Брокеры шарятся и греются по дизайну (15-min idle timeout,app-server-broker.mjs:10-11). Чистка зависит от того, что Claude доставит SessionEnd — резкая смерть процесса может её обойти. Накопленные orphan'ы в снапшоте — это test-harness leak (адресован test-only в PR test: reap orphaned brokers and clean temp dirs on npm test exit (closes openai#163) #5), не runtime-поведение.Итог
Это gap в поведенческом контракте rescue-команды/агента, а не баг брокера или runtime. Контракт не делал Bash-tool-timeout / авто-бэкграунд терминальным — субагент мог «восстановиться» вторым (polling) Bash-вызовом, который и завис.
Фикс — PR #8
--background(rescue работа open-ended; foreground-ран, который хост авто-бэкграундит на timeout, нельзя превратить обратно в foreground wait — поэтому стартуем в background).--waitвсё ещё явно выбирает foreground.taskBash-вызов вернулся по host Bash-tool timeout, субагент не делает второго вызова (никакихstatus/result/cat/sleep/until-grep). Codex-ход продолжает работать и поднимается caller'ом через/codex:status//codex:result— никогда субагентом.commands.test.mjsобновлён под новый контракт (8/8 зелёные).Брокер cwd-routing, runtime SessionEnd и test-only teardown из PR #5 намеренно не тронуты — они корректны как есть.
Закрываю этим PR'ом. Тело issue оставляю как написано — для provenance (видно, с чего начали и куда пришли).
axisrow commentedon Jul 19, 2026
Thanks for the code-traced diagnosis — and you're right, the hang was on my side, not the companion/broker/runtime. Acknowledging the errors in my original report:
codex-rescue.md:22-30forbids polling and mandates a singleBashcall; thesleep/until greploop was generated by the rescue subagent (i.e. my side) after the host auto-backgrounded its foregroundtaskon the Bash-tool timeout. The companion never polled.The real gap — the rescue contract didn't make the host Bash-tool-timeout terminal, so the subagent "recovered" with a second polling call that hung — is exactly what PR #8 fixes (
--backgroundby default + terminal-on-timeout, no second call). Looks correct.Side-effect for me: my cycle-review skill (step 4) launches
adversarial-review --backgroundand then waits in-process — but per your contract the caller is the one who later fetches via/codex:status//codex:result. My step-4 instructions don't do that fetch, which is why today's run on PR #11 also surfaced no Codex result. I'll fix that on my side (the skill), separate from this issue.Closing this as resolved by PR #8. Thanks again for the careful trace.
fix(rescue): default to --background, make Bash-tool timeout terminal (…