Skip to content

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

Closed

Description

@mklikushin

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.

Activity

  1. added
    CLIIssues related to the Codex CLI
    hooksIssues related to event hooks
    tool-callsIssues related to tool calling
    on Apr 3, 2026
  2. removed
    CLIIssues related to the Codex CLI
    tool-callsIssues related to tool calling
    on Apr 3, 2026
  3. github-actions commented on Apr 3, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  4. aashish-thapa commented on Apr 4, 2026

    @aashish-thapa

    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

  5. added a commit that references this issue on Apr 5, 2026
    7799360
  6. henryl commented on Apr 12, 2026

    @henryl

    This would be helpful.

  7. TyceHerrman commented on Apr 14, 2026

    @TyceHerrman

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

  8. horiacristescu commented on Apr 15, 2026

    @horiacristescu

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

  9. 33 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghooksIssues related to event hooks

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions