ApplyPatchHandler doesn't emit PreToolUse/PostToolUse hook event. Hooks only fire for Bash tool.

Resolved 💬 9 comments Opened Apr 3, 2026 by mklikushin Closed Apr 22, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

0.118.0

What subscription do you have?

ChatGPT Plus

Which model were you using?

_No response_

What platform is your computer?

Darwin 24.6.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

_No response_

What issue are you seeing?

PreToolUse and PostToolUse hooks registered via hooks.json never fire for file writes made through apply_patch. They only fire for Bash/shell tool calls.

Two root causes in the CLI source:

ApplyPatchHandler in registry.rs uses the default None implementation for pre_tool_use_payload() / post_tool_use_payload() (lines 63-65 of the trait), so the hook runtime skips it entirely.

hook_runtime.rs hardcodes tool_name: "Bash" (line 131) in both run_pre_tool_use_hooks and run_post_tool_use_hooks, so even if payloads were emitted, they'd be misidentified as Bash calls.

This means hook-based file access control, audit logging, or write coordination cannot cover the primary file write path.

What steps can reproduce the bug?

  1. Create a hooks.json with a PreToolUse hook command that logs all invocations to a file
  2. Ask Codex to edit a source file (triggers apply_patch)
  3. Ask Codex to run a shell command (triggers Bash)
  4. Check the log, observe only the Bash call appears and the file edit is missing

Tested with Codex CLI v0.118.0. Hook log captured 4 entries (2 PreToolUse + 2 PostToolUse), all with tool_name: "Bash" for sed and rg calls. The apply_patch call that actually modified the file produced zero hook events.

What is the expected behavior?

PreToolUse and PostToolUse hooks should fire for apply_patch with tool_name: "ApplyPatch" (or similar) and a payload containing the file paths being modified, consistent with how Bash tool calls emit hook events.

Additional information

We're building multi-agent orchestration that coordinates file writes across Codex, Claude Code, and OpenCode. Claude Code's SDK fires PreToolUse hooks on Write/Edit/MultiEdit tools, which lets us implement file-level locking between agents. This is foundational for implementing checkpoints in an environment where multiple agents edit the same worktree concurrently. Agents acquire a lock on a file before editing, providing a clear record of what changed and how to undo it.

Codex's hook infrastructure supports the same pattern architecturally, but apply_patch not opting in blocks the integration. The fix is scoped, implement pre_tool_use_payload() on ApplyPatchHandler and parameterize tool_name in hook_runtime.rs. Happy to elaborate on the use case or provide more analysis.

View original on GitHub ↗

9 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #16246

Powered by Codex Action

aashish-thapa · 3 months ago

I dug into this and can confirm both root causes described in the issue. Here's a breakdown of the fix I have ready locally:

Root cause 1: hook_runtime.rs hardcodes tool_name: "Bash"

Both run_pre_tool_use_hooks() (line 131) and run_post_tool_use_hooks() (line 162) construct their hook request with tool_name: "Bash".to_string(). This means every hook event is misidentified as a Bash call regardless of the actual tool.

Fix: Add a tool_name: String parameter to both functions and pass it from dispatch_any() in registry.rs using invocation.tool_name. This way every tool gets correctly identified — not just Bash.

Root cause 2: ApplyPatchHandler doesn't implement hook payload methods

ApplyPatchHandler in tools/handlers/apply_patch.rs relies on the default None return from pre_tool_use_payload() and post_tool_use_payload() in the ToolHandler trait (registry.rs:63-74). Since both return None, the hook dispatch in dispatch_any() skips hooks entirely for apply_patch calls.

Fix: Implement both methods on ApplyPatchHandler, following the same pattern as ShellHandler (shell.rs:206-221):

  • Add a helper apply_patch_payload_command() that extracts the patch input from ToolPayload::Function (via ApplyPatchToolArgs) or ToolPayload::Custom
  • pre_tool_use_payload() returns PreToolUsePayload { command } with the patch input
  • post_tool_use_payload() returns PostToolUsePayload { command, tool_response } with the patch input and result text

Scope

3 files changed, 40 insertions, 2 deletions. cargo check -p codex-core passes clean and existing hook-related tests (shell_pre_tool_use_payload_uses_joined_command, shell_command_pre_tool_use_payload_uses_raw_command, build_post_tool_use_payload_uses_tool_output_wire_value) all pass.

I have the implementation on a branch if it would be helpful: https://github.com/aashish-thapa/codex/tree/fix/apply-patch-hook-events

henryl · 3 months ago

This would be helpful.

TyceHerrman · 3 months ago

This issue could be expanded to be about tracking expansion of matcher patterns for pretooluse/posttooluse generally.

horiacristescu · 3 months ago

Please implement the ApplyPatchHandler/FileEdit hooks, my harness depends on them to enforce workflow discipline. I can't find a workaround.

Nihiue · 3 months ago

PostToolUse currently seems limited to shell/Bash-style tools, so successful apply_patch calls are not exposed to hooks in a stable way.

It would be very helpful if apply_patch triggered PostToolUse as a first-class tool event, with structured patch/file-change metadata, so hook authors can reliably audit or react to file edits without depending on shell-command interception.

sanyathoque · 2 months ago

This bug blocks full hook-based governance for non-Bash write paths, so +1.

A small spec clarification could make the fix more durable:

  • tool_name should reflect the actual tool (ApplyPatch, etc.)
  • payload should include touched paths and operation type when available
  • docs should state whether hook emission is expected for all mutating tools or only a subset

That would make hook consumers less brittle and align better with external review/audit workflows.

claudioemmanuel · 2 months ago

Verified that PR #18391 shipped in Codex 0.123.0 (2026-04-23) and the apply_patch hook fix is working as described. ApplyPatchHandler now correctly emits PreToolUse/PostToolUse with the patch body as tool_input.command and the proper tool_name.

Thank you to everyone who investigated and landed this — the squeez adapter comments have been updated to reflect this as a confirmed partial win. The remaining blocker (updatedInput + read_file/grep hook surface) is tracked in #18491.

oxysoft · 2 months ago

Indexed this hook ticket in the umbrella tracker: #21753

Goal: collect the scattered Codex hook requests and bugs into one parity matrix for Full Claude Code Hook Parity (29+), while preserving this issue as the detailed thread for its specific behavior.