Function-call apply_patch does not route through apply_patch handler/hooks

Open 💬 3 comments Opened Jun 6, 2026 by trotsky1997

Summary

When apply_patch arrives as a Responses function_call rather than a custom_tool_call, Codex does not route it through the existing apply_patch runtime contract. This means hook behavior can diverge from the freeform/custom apply_patch path.

The failing shape is a function call named apply_patch, including the functions. namespace form:

{
  "type": "function_call",
  "namespace": "functions.",
  "name": "apply_patch",
  "arguments": "*** Begin Patch\n*** Add File: file.txt\n+patched\n*** End Patch",
  "call_id": "call-id"
}

Expected

The call should route to the existing apply_patch handler so that:

  • the patch is applied through the same verification/runtime path as custom/freeform apply_patch
  • PreToolUse/PostToolUse hooks see tool_name: apply_patch
  • function-call payloads still produce function_call_output, preserving the original wire shape

Observed

A regression test that emits functions.apply_patch as a function_call fails on the current implementation because the PostToolUse additional context is not injected into the follow-up request.

Candidate fix

I pushed a tested branch here:

https://github.com/trotsky1997/codex/compare/main...trotsky1997:fix-apply-patch-hooks

Commit: 5efd838408df9e5c0aa4d8897c4eb404eb80cf06

The patch:

  • canonicalizes apply_patch function calls in the router to the existing plain apply_patch tool name
  • allows the apply_patch handler to consume ToolPayload::Function while preserving function-call output responses
  • supports both raw patch arguments and JSON { "command": "..." } arguments for hook-facing command extraction
  • adds router, handler, and suite-level hook coverage

I attempted to open a draft PR, but both gh pr create and the GitHub connector were denied by repository permissions.

Verification

Red/green verified in a WSL scratch checkout: the new suite test fails on the old implementation because PostToolUse context is not injected, then passes with the candidate fix.

Passing commands:

cargo test -p codex-core --lib build_tool_call_canonicalizes_apply_patch_function_calls -- --nocapture
cargo test -p codex-core --lib pre_tool_use_payload_ -- --nocapture
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all post_tool_use_records_additional_context_for_apply_patch_function_call -- --nocapture
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all post_tool_use_records_additional_context_for_apply_patch -- --nocapture
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all pre_tool_use_blocks_apply_patch_before_execution -- --nocapture
cargo test -p codex-core --lib function_tools_expose_default_hook_payloads_and_rewrites -- --nocapture
cargo test -p codex-core --lib spawn_agent_function_tools_use_agent_matcher_alias -- --nocapture
cargo fmt -p codex-core -- --check
git diff --check

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗