ApplyPatchHandler doesn't emit PreToolUse/PostToolUse hook event. Hooks only fire for Bash tool.
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?
- Create a hooks.json with a PreToolUse hook command that logs all invocations to a file
- Ask Codex to edit a source file (triggers apply_patch)
- Ask Codex to run a shell command (triggers Bash)
- 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.
9 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
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.rshardcodestool_name: "Bash"Both
run_pre_tool_use_hooks()(line 131) andrun_post_tool_use_hooks()(line 162) construct their hook request withtool_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: Stringparameter to both functions and pass it fromdispatch_any()inregistry.rsusinginvocation.tool_name. This way every tool gets correctly identified — not just Bash.Root cause 2:
ApplyPatchHandlerdoesn't implement hook payload methodsApplyPatchHandlerintools/handlers/apply_patch.rsrelies on the defaultNonereturn frompre_tool_use_payload()andpost_tool_use_payload()in theToolHandlertrait (registry.rs:63-74). Since both returnNone, the hook dispatch indispatch_any()skips hooks entirely for apply_patch calls.Fix: Implement both methods on
ApplyPatchHandler, following the same pattern asShellHandler(shell.rs:206-221):apply_patch_payload_command()that extracts the patch input fromToolPayload::Function(viaApplyPatchToolArgs) orToolPayload::Custompre_tool_use_payload()returnsPreToolUsePayload { command }with the patch inputpost_tool_use_payload()returnsPostToolUsePayload { command, tool_response }with the patch input and result textScope
3 files changed, 40 insertions, 2 deletions.
cargo check -p codex-corepasses 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
This would be helpful.
This issue could be expanded to be about tracking expansion of matcher patterns for pretooluse/posttooluse generally.
Please implement the ApplyPatchHandler/FileEdit hooks, my harness depends on them to enforce workflow discipline. I can't find a workaround.
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.
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_nameshould reflect the actual tool (ApplyPatch, etc.)That would make hook consumers less brittle and align better with external review/audit workflows.
Verified that PR #18391 shipped in Codex 0.123.0 (2026-04-23) and the
apply_patchhook fix is working as described.ApplyPatchHandlernow correctly emitsPreToolUse/PostToolUsewith the patch body astool_input.commandand the propertool_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/grephook surface) is tracked in #18491.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.