PreToolUse hook with Bash matcher silently not invoked (UserPromptSubmit + PostToolUse hooks fire fine)
Summary
A PreToolUse hook configured in ~/.claude/settings.json with matcher: "Bash" is not invoked on real Bash tool calls (including git commit), even though the same configuration's UserPromptSubmit and PostToolUse hooks fire correctly.
Manual invocation of the hook script with the exact same JSON payload the runtime would pass produces the expected behavior (block with exit 2). So the hook script is correct; the runtime's invocation is the gap.
Environment
- Claude Code CLI (whichever version was current 2026-04-23/24 in this user's install — running
claude --versionwould confirm) - macOS Darwin 24.6.0
- Hook script at
~/.claude/hooks/pre-commit-gate.sh(executable, valid shebang#!/usr/bin/env bash)
Configuration
~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/pre-commit-gate.sh"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/karpathy-reinjection.sh"
}
]
}
]
}
}
A separate project-local .claude/settings.json defines a PostToolUse Bash hook (with if: "Bash(git push:*)" clause) that fires on every Bash tool call.
Reproduction
- Add a debug write to the top of
~/.claude/hooks/pre-commit-gate.sh:
``bash``
{ printf '%s\n' "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] hook fired pid=$$ ppid=$PPID cwd=$PWD"; } >> /tmp/hook-debug.log 2>/dev/null || true
- Have Claude run a Bash tool call that contains
git commit -m "test". - Observe
/tmp/hook-debug.logis never created — hook was not invoked. - Manually pipe the equivalent payload into the hook:
``bash``
echo '{"tool_name":"Bash","tool_input":{"command":"cd /repo/path && git commit -m test"}}' | ~/.claude/hooks/pre-commit-gate.sh
echo "exit=$?"
→ exit 2, prints "Staged changes have drifted from the last verified state" message — hook itself works correctly.
Expected
PreToolUse Bash hook should fire on every Bash tool call, mirroring how PostToolUse Bash hook fires.
Actual
Only UserPromptSubmit and PostToolUse hooks fire. PreToolUse Bash hook is silently inert. No error message in any logs visible to the user.
Impact
Skill-based commit gates (e.g., verify-before-commit skill that writes a per-repo marker the hook validates at commit time) are silently bypassed. Users believe their gate is enforced; in reality it isn't. CI catches the actual issues but the local pre-commit signal is lost.
Workaround
Manually invoke the gate skill before each commit. Awkward; defeats the purpose of an automated hook.
Notes
- Tilde expansion (
~/...) works for the other hook types (UserPromptSubmit script also uses~). - Tested with both
~/...and absolute path forcommand:— neither caused the PreToolUse hook to fire. - The hook script returns exit 0 for non-
git commitBash calls (immediate pass-through), so even if it were firing on every Bash call, it would only block actualgit commitattempts.
Asks
- Confirm whether
PreToolUse+Bashmatcher combination is supported in current Claude Code. - If supported, identify the runtime path that's skipping invocation.
- If unsupported / deprecated, document the migration path (e.g., is
Bash(git commit:*)the new matcher syntax?).
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗