apply_patch / file_change tool deadlocks non-deterministically on file mutations (kind:add ~100%, kind:update ~50%) across codex CLI 0.125→0.133
Summary
The apply_patch / file_change tool in codex exec can deadlock non-deterministically on file mutations. Affected items emit item.started with status: "in_progress" but the matching item.completed event never arrives. turn.completed is never emitted. sandbox_errors stays empty. The spawn must be SIGKILL'd at the wall-clock timeout.
The behavior is probabilistic and variable. In one investigation session (2026-05-25 ~01:00-03:00 PDT), file_change kind:"add" wedged 5/5 times on a minimal repro. In a follow-up session ~18 hours later on the same machine / same codex version, 20/20 spawns of the same repro completed successfully. Both sessions used identical codex CLI versions, identical TOML config, identical briefs. So the bug is intermittent, but when it happens it is reliably a deadlock (not a slow path).
Observed wedge breakdown across the first session (n=11 file-mutation spawns):
kind: "add"(new file): 5/5 wedgeskind: "update"(in-place edit): 1/2 wedges (one success, one wedge on same file)command_executionshell-write (control): 0/4 wedges
Affected versions
Tested at both endpoints of the available range. Both wedge identically when the deadlock fires:
@openai/codex@0.125.0(pre-jump baseline)@openai/codex@0.133.0(currentlateston npm at investigation time)
The bug is not a regression in the 0.125→0.133 range — both endpoints exhibit the same behavior. Mid-range versions (0.126/0.127 are skipped on npm; 0.128/0.129/0.130/0.131/0.132 were not bisected).
Minimal reproduction
echo "Create a new TypeScript file at exactly /tmp/_codex_repro.ts containing:
const probe = \"hello\";
That's all. One file, one line. Use the apply_patch (file_change) tool to create the file." \
| codex exec --model gpt-5.5 \
-c model_reasoning_effort=high \
-c approval_policy=never \
-c sandbox_workspace_write.writable_roots='["/tmp"]' \
--sandbox workspace-write \
--skip-git-repo-check \
--cd /tmp \
--json -o /tmp/_codex_repro.final.txt -
When the wedge fires: the process hangs after the file_change item enters status: "in_progress". No subsequent item.completed event. No turn.completed. The file is not created. The hang is indefinite (we observed past 600s).
Evidence (wedged spawn)
JSONL event stream tail at the wedge point:
{"type":"thread.started","thread_id":"019e5cdf-69f0-7843-915d-e4be971089a5"}
{"type":"turn.started"}
{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"I'll create the requested file ..."}}
{"type":"item.started","item":{"id":"item_1","type":"command_execution","command":"/bin/zsh -lc 'mkdir -p /tmp'","status":"in_progress"}}
{"type":"item.completed","item":{"id":"item_1","type":"command_execution","command":"/bin/zsh -lc 'mkdir -p /tmp'","exit_code":0,"status":"completed"}}
{"type":"item.started","item":{"id":"item_2","type":"file_change","changes":[{"path":"/tmp/_codex_repro.ts","kind":"add"}],"status":"in_progress"}}
[NO FURTHER EVENTS — process hangs until SIGKILL]
A small audit script (VerifyForgeExecution.ts) we use on every spawn returns:
{
"codex_executed": false,
"reason": "codex started but no turn completed — likely premature termination",
"events_seen": 6,
"turn_completed_count": 0,
"item_completed_count": 2,
"sandbox_errors": []
}
Note sandbox_errors: [] — this is not a sandbox or permissions issue. The writable_roots config is correctly applied; approval_policy=never is correctly applied; command_execution items in the same spawn complete cleanly. The bug is downstream of the sandbox layer.
Non-determinism evidence
The same file, same kind: "update" operation, same brief shape, can produce different outcomes across spawns within a single session:
- One spawn:
file_change kind:"update"completes in 17s, file correctly modified,turn.completedpresent - Next spawn (same target, same brief):
file_change kind:"update"wedges at 90s, file unchanged, noturn.completed
And across sessions: a brief that wedged 5/5 in one session succeeded 20/20 in the next session 18 hours later.
This rules out a deterministic input-shape trigger. It looks like an interaction with backend state (server side?) or system load.
Hypotheses we ruled out
- Sandbox / writable_roots:
command_executionitems succeed on the same target paths with the same config;sandbox_errorsstays empty during wedge. Bug is downstream of sandbox. - Path-subtree specificity: wedges fire identically on
/tmp/*.tsand~/.claude/**/*.ts. - Version regression in 0.125→0.133: both endpoints exhibit the bug identically.
- TOML override interaction:
codex features listexposes no flag that disables the apply_patch tool in the affected range; tested with and withoutsandbox_workspace_write.writable_rootsoverrides.
Workaround (downstream)
Prepending a steering advisory to the brief — instructing the model to use command_execution shell-write (printf > path, cat <<EOF, sed -i, python heredoc) for all file operations instead of file_change — empirically eliminates most wedges. In our wrapper harness:
- Pre-advisory: ~80-100% wedge rate on
kind:add(varies by session) - Advisory only: ~82% success rate (historical average across sessions)
- Advisory + wedge-detect-and-retry harness (SIGTERM at 30s in_progress + one re-spawn with stronger preamble): ~97% combined success
This is a downstream workaround. The upstream apply_patch tool deadlock remains.
Environment
- macOS Darwin 25.3.0 (arm64)
- codex CLI 0.125.0, 0.128.0, 0.133.0 (all three exhibit the bug)
- Model: gpt-5.5, reasoning_effort=high
- Sandbox: workspace-write with TOML
writable_rootsoverride - Approval policy: never (non-interactive
codex exec)
Investigation
Investigation conducted with hypothesis-plural probes across four layers:
- H1 — apply_patch deadlock on kind:add → refined to "non-deterministic across kinds, not subtree-specific"
- H2 — workspace-write file lock → refuted via shell-write control
- H3 — version regression → refuted via 0.125.0 + 0.133.0 endpoint test
- H4 — TOML override interaction → refuted via codex features recon
19 production spawns + 7 unit-test assertions captured in our work tree. Full evidence chain (events files, hypothesis probes, control conditions) available on request.
Asks
- Confirm whether this is a known issue in the
apply_patchtool implementation. - If known: any expected fix timeline.
- If new: happy to provide additional repro environments, full events files, or run targeted probes against pre-release builds. We have a small audit harness that can capture full JSONL event streams + timing per spawn.
Thanks for codex — the rest of the tool works very well; this single failure mode has been a recurring tax for non-interactive automation harnesses.
3 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Proposed Solution
Solution: openai/codex #24407 — apply_patch/file_change Tool Deadlocks Non-Deterministically
Issue Summary
---
Root Cause Analysis
The Problem
The
apply_patch/file_changetool incodex execcan deadlock non-deterministically on file mutations. Theitem.startedevent fires withstatus: "in_progress"butitem.completednever arrives.turn.completedis never emitted. The spawn must be SIGKILL'd.Observed Behavior
kind: "add"(new file): 5/5 wedges in first session, 0/20 in next session (same machine, same version)kind: "update"(in-place edit): 1/2 wedgescommand_executionshell-write (control): 0/4 wedgesRuled Out Hypotheses
command_executionsucceeds on same paths;sandbox_errorsempty/tmp/*.tsand~/.claude/**/*.tsMost Likely Root Cause
The non-determinism pattern (5/5 wedges in one session, 0/20 in next) strongly suggests a race condition in the file_change event processing pipeline, likely involving:
The fact that
command_executionsucceeds on the same paths rules out filesystem-level issues. The bug is in theapply_patchtool's event processing code path.---
Fix Approach (4 Components)
Fix 1: File Change Handler Lock Timeout (Primary)
File:
codex-rs/src/tools/file_change.rs(or equivalent)Add a timeout to the file_change handler's internal lock acquisition:
Fix 2: Event Completion Guarantee (Safety Net)
File:
codex-rs/src/event_loop.rs(or equivalent)Ensure every
item.startedhas a correspondingitem.completedoritem.failed:Fix 3: Apply Patch Retry with Exponential Backoff
File:
codex-rs/src/tools/file_change.rsAdd automatic retry for file_change operations that appear stuck:
Fix 4: Backend State Isolation
File:
codex-rs/src/backend/file_ops.rs(or equivalent)If the deadlock involves backend state, ensure each file_change operation uses an isolated transaction:
---
Testing Plan
Unit Tests
item.startedhas matchingitem.completedoritem.failedIntegration Tests
Regression Tests
---
Impact Assessment
codex execwith file_change operations---
Pricing: $2,000–$3,000
I may be seeing the same class of
codex execfile-mutation deadlock on a newer bundled CLI, although my harness did not run with--json, so I cannot confirm the exactfile_change item.startedstate.Environment:
/Applications/Codex.app/Contents/Resources/codexcodex-cli 0.138.0-alpha.7gpt-5.5codex execneverworkspace-write--ephemeral --ignore-user-config --ignore-rules --disable pluginsThe parent benchmark harness launches
codex execin a fresh isolated git clone and checks once per second for repository changes via:Observed no-edit records:
| Shape | Duration | Seconds since last output | Changed files |
| --- | ---: | ---: | ---: |
| post-output/pre-edit | 240.019s | 27.496s | 0 |
| post-planning/pre-edit | 240.019s | 36.146s | 0 |
| startup/no-output | 240.050s | 239.665s | 0 |
| post-output/pre-edit | 240.033s | 27.426s | 0 |
Representative post-output log:
A control run using the same runner, same target refs, and same watchdogs but a Claude adapter reached real repository edits in both records, with first repo changes at 85.1s and 92.1s. That suggests the no-edit path is specific to the Codex execution path rather than required by the runner, target repositories, or watchdog mechanics.
Caveat: I also observed a separate startup/no-output shape with only the Codex banner, echoed prompt, and repeated
state db discrepancy ... falling_backwarnings. That may be a separate failure mode from the file-change deadlock tracked here.---
Update (2026-06-14): I traced a plausible lower-level hang path and prepared a local patch.
The
file_changeevent is emitted before the apply-patch runtime awaits sandboxed file-system operations. Completion is only emitted after the runtime returns. In the sandboxed path, those file operations go throughexec-server/src/fs_sandbox.rs, which spawns the Codex fs helper, writes the request JSON to stdin, shuts stdin down, and then awaitschild.wait_with_output()with no timeout.If the fs helper or platform sandbox wedges during startup, stdin processing, or completion,
apply_patchcan await forever. That matches the observed stream shape:file_change item.startedwith noitem.completedand noturn.completed.The local patch I tested bounds the whole helper subprocess interaction with a 60s timeout, relying on the existing
kill_on_drop(true)on the helper process when the future is dropped. It adds a Unix regression test with a hung helper command and keeps the change incodex-exec-serverrather thancodex-core.Validation run locally:
just test -p codex-exec-server: 218 passed, 2 skippedjust fmtjust fix -p codex-exec-serverPatch branch now available here: https://github.com/baskduf/codex/tree/codex/fix-apply-patch-helper-timeout
I attempted to open a draft PR against
openai/codex, but bothgh pr createand the GitHub connector are blocked by repository/integration permissions. The fork branch is pushed and ready if maintainers want to pull it or invite a PR.