Summary
With the cx (Codex) backend in worktree mode, the worker's turn dies with
patch rejected: writing outside of the project; rejected by user approval settings.
The runner then reports worktree runner failed (code 1) and writes no
verdict.json — but the work itself is usually complete and sitting in the
worktree. An orchestrator that trusts the exit code concludes the run failed
and discards finished work; one that trusts a stale verdict.json from an
earlier run reads the wrong verdict entirely.
Hit 2 out of 3 runs in one session (2026-08-22).
Reproduction
cli-dispatch-run --backend cx --cwd <repo> \
--prompt "Read the file /private/tmp/.../BRIEF.md and execute it exactly as written." \
--verify 'sh backend/scripts/verify_backend_in_worktree.sh'
The brief lives outside the repo (a scratchpad path). Reading it is fine; the
failure comes later, when the worker tries to WRITE somewhere outside the
project — a temp file, or a report next to the brief.
Observed output (trimmed):
cx-stream → Codex (OpenAI Codex CLI) worker
cwd: /tmp/cx-wt-sarltQ
sandbox: workspace-write (network: on)
2026-08-22T01:17:37Z ERROR codex_core::tools::router: error=patch rejected: writing outside of the project; rejected by user approval settings
cx-stream: codex turn failed: ...
cli-dispatch-run: worktree runner failed (code 1)
Meanwhile:
$ git -C /tmp/cx-wt-sarltQ status --short
M CLAUDE.md
M backend/app/api/v1/rewards.py
M backend/app/services/reward_service.py
?? backend/app/services/reference_anchor.py
?? backend/tests/test_reward_engine_routing.py
?? worker-report.json
The task was done. Running the --verify command by hand in that worktree
passed (1881/1882 tests; the one failure was a genuine finding, unrelated to
this bug).
Why it matters
--verify never runs, so the run produces no verdict at all — not a failing
one, none. The two failure modes look identical from outside:
- worker died having done nothing → correctly discarded
- worker died having finished → work silently thrown away
Both print code 1. The only way I found to tell them apart is inspecting the
worktree manually after every run, which is exactly the babysitting the
deterministic runner is meant to remove.
There is a sharper version: on one run the wrapper reported success while the
worker had changed nothing, because a --verify on an unmodified tree
passes trivially. Combined with this issue, exit code alone is not a signal in
either direction.
Suggestions
- Always write
verdict.json, including when the worker turn dies — with
the diffstat and a state that distinguishes "worker died, tree dirty" from
"worker died, tree clean". That alone makes the outcome recoverable.
- Run
--verify anyway when the tree is dirty after a worker death, and
report the result. If it passes, the run arguably succeeded.
- Consider a distinct exit code for "worker died but left changes", so a
caller can branch without parsing logs.
- Possibly: pass the brief's directory to the sandbox as writable, or document
that briefs should be copied INTO the repo first. Telling the worker in the
brief not to write outside the repo did not prevent it (tried; it still
attempted it on the very next run).
Environment
- backend:
cx (Codex CLI), model gpt-5.6-sol, sandbox workspace-write (network on)
- mode: worktree (
--cwd was a normal checkout, runner created /tmp/cx-wt-*)
- also set:
CLI_DISPATCH_ALLOW_CONCURRENT_EDITS=1 on two of the three runs
Summary
With the
cx(Codex) backend in worktree mode, the worker's turn dies withpatch rejected: writing outside of the project; rejected by user approval settings.The runner then reports
worktree runner failed (code 1)and writes noverdict.json— but the work itself is usually complete and sitting in theworktree. An orchestrator that trusts the exit code concludes the run failed
and discards finished work; one that trusts a stale
verdict.jsonfrom anearlier run reads the wrong verdict entirely.
Hit 2 out of 3 runs in one session (2026-08-22).
Reproduction
The brief lives outside the repo (a scratchpad path). Reading it is fine; the
failure comes later, when the worker tries to WRITE somewhere outside the
project — a temp file, or a report next to the brief.
Observed output (trimmed):
Meanwhile:
The task was done. Running the
--verifycommand by hand in that worktreepassed (1881/1882 tests; the one failure was a genuine finding, unrelated to
this bug).
Why it matters
--verifynever runs, so the run produces no verdict at all — not a failingone, none. The two failure modes look identical from outside:
Both print
code 1. The only way I found to tell them apart is inspecting theworktree manually after every run, which is exactly the babysitting the
deterministic runner is meant to remove.
There is a sharper version: on one run the wrapper reported success while the
worker had changed nothing, because a
--verifyon an unmodified treepasses trivially. Combined with this issue, exit code alone is not a signal in
either direction.
Suggestions
verdict.json, including when the worker turn dies — withthe diffstat and a
statethat distinguishes "worker died, tree dirty" from"worker died, tree clean". That alone makes the outcome recoverable.
--verifyanyway when the tree is dirty after a worker death, andreport the result. If it passes, the run arguably succeeded.
caller can branch without parsing logs.
that briefs should be copied INTO the repo first. Telling the worker in the
brief not to write outside the repo did not prevent it (tried; it still
attempted it on the very next run).
Environment
cx(Codex CLI), modelgpt-5.6-sol, sandboxworkspace-write(network on)--cwdwas a normal checkout, runner created/tmp/cx-wt-*)CLI_DISPATCH_ALLOW_CONCURRENT_EDITS=1on two of the three runs