codex - 💡(How to fix) Fix apply_patch sandbox retry can request duplicate approval and hang app-server turns [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openai/codex#21117Fetched 2026-05-05 05:53:23
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
labeled ×4commented ×1

apply_patch can request a second approval during the sandbox retry path even after the patch was already config/auto-approved. In app-server or wrapper contexts where that internal retry approval is not answered, the turn can wait indefinitely without producing a tool result or turn/completed.

I have a tested fix on this branch/commit:

I could not open a PR because this account/token is blocked from CreatePullRequest against openai/codex, even though mzhaom/codex is a real fork and the branch is comparable.

Error Message

A run got stuck after Codex core accepted and internally dispatched an apply_patch freeform custom tool call. The model response completed and Codex logged the tool as config-approved, but there was no corresponding custom_tool_call_output, no final assistant message, and no turn/completed event.

Root Cause

Tracing the core tool path showed:

  1. The Responses API emitted a freeform custom_tool_call for apply_patch.
  2. Codex core accepted it and routed it through ToolRouter::dispatch_tool_call_with_code_mode_result into ApplyPatchHandler.
  3. apply_patch::apply_patch assessed the patch as safe and returned ExecApprovalRequirement::Skip.
  4. ToolOrchestrator logged the tool decision as config-approved.
  5. The first sandboxed attempt can fail when the active workspace-write policy does not allow the target write.
  6. ApplyPatchRuntime allows a no-sandbox retry under AskForApproval::OnRequest.
  7. The orchestrator did not remember the earlier Skip decision as already_approved, so the retry path requested a second approval.
  8. In app-server/wrapper contexts that do not answer that retry approval, the tool call never produces a result and the turn never completes.

The bug is the lost approval state between the initial config-approved Skip and the later sandbox retry decision.

Fix Action

Fix / Workaround

apply_patch can request a second approval during the sandbox retry path even after the patch was already config/auto-approved. In app-server or wrapper contexts where that internal retry approval is not answered, the turn can wait indefinitely without producing a tool result or turn/completed.

A run got stuck after Codex core accepted and internally dispatched an apply_patch freeform custom tool call. The model response completed and Codex logged the tool as config-approved, but there was no corresponding custom_tool_call_output, no final assistant message, and no turn/completed event.

Code Example

fn skip_requirement_counts_as_approval(&self, _req: &Req) -> bool {
    false
}

---

assertion failed: `(left == right)`
left: 1
right: 0

---

cargo fmt --package codex-core
cargo test -p codex-core config_approved_tool_retry_does_not_request_second_approval
cargo test -p codex-core apply_patch
cargo test -p codex-app-server turn_start_does_not_stream_apply_patch_change_updates_without_feature_v2
git diff --check
RAW_BUFFERClick to expand / collapse

Summary

apply_patch can request a second approval during the sandbox retry path even after the patch was already config/auto-approved. In app-server or wrapper contexts where that internal retry approval is not answered, the turn can wait indefinitely without producing a tool result or turn/completed.

I have a tested fix on this branch/commit:

I could not open a PR because this account/token is blocked from CreatePullRequest against openai/codex, even though mzhaom/codex is a real fork and the branch is comparable.

Observed behavior

A run got stuck after Codex core accepted and internally dispatched an apply_patch freeform custom tool call. The model response completed and Codex logged the tool as config-approved, but there was no corresponding custom_tool_call_output, no final assistant message, and no turn/completed event.

The target worktree remained clean and the expected patched file was not created, so this was not just client-side rendering or wrapper output handling.

Root cause

Tracing the core tool path showed:

  1. The Responses API emitted a freeform custom_tool_call for apply_patch.
  2. Codex core accepted it and routed it through ToolRouter::dispatch_tool_call_with_code_mode_result into ApplyPatchHandler.
  3. apply_patch::apply_patch assessed the patch as safe and returned ExecApprovalRequirement::Skip.
  4. ToolOrchestrator logged the tool decision as config-approved.
  5. The first sandboxed attempt can fail when the active workspace-write policy does not allow the target write.
  6. ApplyPatchRuntime allows a no-sandbox retry under AskForApproval::OnRequest.
  7. The orchestrator did not remember the earlier Skip decision as already_approved, so the retry path requested a second approval.
  8. In app-server/wrapper contexts that do not answer that retry approval, the tool call never produces a result and the turn never completes.

The bug is the lost approval state between the initial config-approved Skip and the later sandbox retry decision.

Fix on branch

The branch adds a small explicit hook to the Approvable trait:

fn skip_requirement_counts_as_approval(&self, _req: &Req) -> bool {
    false
}

The default is false, preserving current behavior for other tools. ApplyPatchRuntime opts in because its upstream safety assessment has already approved the patch operation before execution is delegated to the runtime.

ToolOrchestrator now sets already_approved from that hook in the non-guardian ExecApprovalRequirement::Skip path. That lets apply_patch retry outside the sandbox without emitting a duplicate approval request.

Changed files:

  • codex-rs/core/src/tools/sandboxing.rs
  • codex-rs/core/src/tools/orchestrator.rs
  • codex-rs/core/src/tools/runtimes/apply_patch.rs
  • codex-rs/core/src/session/tests.rs

Repro test

The branch adds config_approved_tool_retry_does_not_request_second_approval, which uses a fake runtime that:

  • is config-approved via ExecApprovalRequirement::Skip
  • fails the first sandboxed attempt with SandboxErr::Denied
  • allows a no-sandbox retry
  • counts approval requests

With only the orchestrator propagation line removed, the test fails exactly as expected:

assertion failed: `(left == right)`
left: 1
right: 0

That proves the original behavior requested one unexpected second approval. With the fix restored, the same test passes.

Validation run

Local validation:

cargo fmt --package codex-core
cargo test -p codex-core config_approved_tool_retry_does_not_request_second_approval
cargo test -p codex-core apply_patch
cargo test -p codex-app-server turn_start_does_not_stream_apply_patch_change_updates_without_feature_v2
git diff --check

Results:

  • focused regression test passed with the fix
  • pre-fix reproduction failed with one unexpected retry approval
  • cargo test -p codex-core apply_patch passed: 293 tests
  • app-server apply_patch turn completion test passed
  • git diff --check passed

extent analysis

TL;DR

The most likely fix is to apply the patch from the provided branch, which adds an explicit hook to the Approvable trait to preserve the approval state between the initial config-approved Skip and the later sandbox retry decision.

Guidance

  • Review the changes in the provided branch, specifically the addition of the skip_requirement_counts_as_approval method to the Approvable trait.
  • Verify that the ToolOrchestrator now correctly sets already_approved based on the new hook, preventing duplicate approval requests.
  • Test the fix using the provided config_approved_tool_retry_does_not_request_second_approval test to ensure it passes with the fix and fails without it.
  • Run the local validation commands to ensure the fix does not introduce any other issues.

Example

No code snippet is provided as the fix is already implemented in the given branch.

Notes

The fix is specific to the ApplyPatchRuntime and may not apply to other tools. The provided branch and commit should be reviewed and tested thoroughly before applying the fix.

Recommendation

Apply the workaround by merging the provided branch into the main codebase, as it fixes the specific issue with the apply_patch tool and prevents duplicate approval requests.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

codex - 💡(How to fix) Fix apply_patch sandbox retry can request duplicate approval and hang app-server turns [1 comments, 2 participants]