PostToolUse hook `if: Bash(<head> *)` matcher fires on commands that don't match
Summary
A PostToolUse hook with if: "Bash(git commit *)" fires on Bash commands that do not start with git commit. The if: clause is supposed to gate execution, but empirically the hook script runs for unrelated commands.
Repro
~/.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/test-trigger.sh",
"if": "Bash(git commit *)"
}
]
}
]
}
}
~/.claude/hooks/test-trigger.sh:
#!/bin/bash
set -euo pipefail
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
echo "FIRED ON: $COMMAND" >> /tmp/hook-misfire.log
Then run any non-git commit Bash command via the agent — e.g.:
iis-nexus knowledge resolve 1234 --resolution disprovengit statusgh issue list
Each appears in /tmp/hook-misfire.log despite if: "Bash(git commit *)".
Expected
The if: clause should gate the hook to only fire when the executed command matches Bash(git commit *) — i.e. the command starts with git commit. Other Bash commands should not invoke the hook script at all.
Actual
The hook fires on every Bash command regardless of the command's content.
Impact
Hooks that produce hookSpecificOutput (e.g. UserPromptSubmit-style context injection on PostToolUse) end up injecting their context after every Bash command. In our case, a "code committed" reflection prompt was being injected after iis-nexus knowledge resolve, git status, and other non-commit commands, creating significant noise in the agent's context.
Workaround
Self-guard at the script head:
TRIMMED="${COMMAND#"${COMMAND%%[![:space:]]*}"}"
case "$TRIMMED" in
"git commit"|"git commit "*) ;;
*) exit 0 ;;
esac
This works but defeats the purpose of if: — every hook now has to re-implement matcher logic to be safe.
Environment
- Claude Code 2.1.139
- macOS 25.4.0 (Darwin)
- Confirmed by 17/17 regression tests against a hardened script vs. the original
Suggestion
Either:
- Document
if:clauses as performance hints only (not correctness gates), so authors know to self-guard. - Fix the matcher so it actually gates the hook invocation.
Option 2 is preferable — option 1 makes the if: clause largely useless.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗