PreToolUse hooks on the `Bash` matcher silently and persistently stop firing partway through a session

Open 💬 0 comments Opened Jul 10, 2026 by sourcesmith

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary

With two PreToolUse hooks registered on overlapping Bash matchers in settings.json, both hooks fire correctly for the first several matching Bash tool calls in a session, then — without any error, warning, or apparent trigger — stop being invoked at all for the remainder of the session. Once it starts, the failure is persistent (every subsequent matching call is affected, not just an intermittent one), and does not recover.

Observed behavior

In a single session:

  1. The first several git diff <args> invocations correctly triggered the first hook's rewrite (confirmed via the tool's own distinctive output).
  2. Partway through the same session, with no configuration change and no apparent trigger, git diff invocations began running as completely unmodified, raw git — neither hook's effect was applied.
  3. This was confirmed to not be a hook-script problem: piping the exact same tool_input.command JSON directly into the first hook script (echo '{"tool_input":{"command":"..."}}' | python3 ~/.claude/hooks/sem-git-rewrite) at the same moment the live call was misbehaving always returned the correct rewrite. The script's logic was correct and deterministic throughout; only the harness's live invocation of it (or dispatch to it) stopped taking effect.
  4. Once the failure began, it did not self-recover for the rest of the session — verified across multiple different files and slightly different command phrasings.
  5. The second hook (a separate tool's own Bash-output post-processing, unrelated to the first hook's logic) stopped taking effect at the same point too, suggesting the fault is in hook dispatch for the matcher generally, not specific to either hook's own code.

What Should Happen?

PreToolUse hooks registered on a matcher should be invoked consistently for every matching tool call for the duration of a session, or any internal failure to do so should surface as a visible error/warning rather than silently degrading to "hook did not run."

Error Messages/Logs

Steps to Reproduce

Hook configuration

"PreToolUse": [
  {
    "matcher": "Bash",
    "hooks": [
      {
        "type": "command",
        "command": "~/.claude/hooks/sem-git-rewrite",
        "if": "Bash(git *)",
        "statusMessage": "Rewriting to sem..."
      }
    ]
  },
  {
    "matcher": "Bash",
    "hooks": [
      {
        "type": "command",
        "command": "rtk hook claude"
      }
    ]
  }
]

Both hooks are registered on the Bash matcher (the first gated further by an if: "Bash(git *)" condition). The first hook rewrites git diff/git blame commands to an alternate tool via updatedInput.command in its JSON output; the second does its own separate rewriting/reformatting of Bash output for token savings.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.205 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Environment

  • Two PreToolUse hooks on the Bash matcher (one further scoped by an if condition), as shown above.
  • OS: Linux (WSL2).
  • Happened during a long, tool-call-heavy session involving many git operations (commits, diffs across a 12-task implementation plan, a rebase, a merge).

Additional context

This was found while debugging what first looked like a bug in one hook script's own logic (a git diff argument-rewriting bug). After fixing and thoroughly verifying that script in isolation (confirmed correct via direct invocation, every time, including during the live failure), the remaining discrepancy pointed at hook dispatch itself rather than either hook's implementation. Happy to provide the hook script for sem if useful for reproduction — the scripts are a personal customization, not part of Claude Code itself, but the dispatch behavior clearly is. The second hook is implemented by the rtk binary.

View original on GitHub ↗