Skip to main content

hook-prefix-matcher-skips-compound-command

A PreToolUse Bash hook that gates on a command PREFIX (`[[ "$COMMAND" != git\ commit* ]] && exit 0`) silently no-ops when you write a compound command like `git add -A && git commit โ€ฆ`. The action succeeds, the hook never ran, and you report "verification passed" when nothing was verified.

Jump to install

Source facts

Repository
blas1n/claude-skills
Last source activity
August 7, 2026 at 18:57
Detected SKILL.md language
English
Stars
2
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
name
hook-prefix-matcher-skips-compound-command
description
A PreToolUse Bash hook that gates on a command PREFIX (`[[ "$COMMAND" != git\ commit* ]] && exit 0`) silently no-ops when you write a compound command like `git add -A && git commit โ€ฆ`. The action succeeds, the hook never ran, and you report "verification passed" when nothing was verified.
category
trap
# Hook prefix matcher skips compound commands ## Problem A common Claude Code guard runs the test suite before a commit: ```bash # ~/.claude/hooks/pre-commit-verify.sh COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""') if [[ "$COMMAND" != git\ commit* ]]; then exit 0 # not a commit โ€” nothing to do fi ... uv run pytest "$TEST_DIR" || { echo "BLOCKED: Unit tests failed." >&2; exit 2; } ``` The matcher tests the **whole command string** against a prefix. So this fires: ```bash git commit -m "..." # matches โ†’ suite runs โ†’ can BLOCK ``` and this does not: ```bash git add -A && git commit -m "..." # does NOT match โ†’ exit 0 โ†’ suite NEVER runs ``` Same for `cd repo && git commit โ€ฆ`, `git add . ; git commit โ€ฆ`, and anything where `git commit` is not the leading token. The failure is silent and *inverted*: the guard exists to stop you, so its silence reads as approval. A commit that sails through feels verified. It isn't. Observed cost: a broken test rode through two commits and was caught only by CI, after the earlier attempt (`git commit` alone) had been correctly blocked โ€” which made the later "pass" look like the fix had worked. ## Detection The hook's own output is the tell. A hook that ran prints its banner in the tool result: ``` [pre-commit] Running pytest... ``` **No banner โ‡’ the hook did not run.** Before claiming a hook verified anything, look for its output. `git commit` succeeding is not evidence. Confirm the matcher directly when unsure: ```bash grep -n 'COMMAND' ~/.claude/hooks/<hook>.sh ``` ## Solution 1. **Own the verification; don't delegate it to a hook.** Run the gate yourself and read the result, then commit: ```bash uv run pytest tests/ -q # you see this pass git add -A git commit -F - <<'MSG' ... MSG ``` 2. **If you want the hook to fire, keep the matched token first.** Stage in a separate call so the commit command starts with `git commit`: ```bash git add -A # call 1 git commit -F - ... # call 2 โ€” hook matches ``` 3. **Never phrase a report as "the hook passed"** unless you saw its banner. Say what you actually ran. 4. **If you own the hook**, make the matcher substring-based and anchor on the real intent: ```bash if ! grep -qE '(^|[;&|]\s*)git\s+commit\b' <<<"$COMMAND"; then exit 0; fi ``` ## Sibling failure: the hook runs but its tooling isn't installed The inverse also bites. In a **fresh git worktree** the venv has no dev dependencies, so the hook's `uv run pytest tests/` cannot spawn `pytest`, exits non-zero, and reports: ``` BLOCKED: Unit tests failed. Fix before committing. ``` Nothing failed โ€” nothing ran. Reproduce the hook's exact command before believing its verdict: ```bash uv run pytest tests/ -q # โ†’ "error: Failed to spawn: `pytest`" uv sync --extra dev # then the hook's command works for real ``` Same lesson from the other direction: **a gate's verdict is only meaningful if you know the gate executed the thing it claims to have executed.** ## Generalisation Any guard keyed on surface form rather than effect has this hole โ€” lint hooks matching `^npm run build`, deploy guards matching `^kubectl apply`, audit hooks matching `^psql`. The rule: **a silent guard is indistinguishable from a passing guard, so require positive evidence that it ran.** ## Related - `feedback_prompt_hooks_crash` โ€” hook config shape (command type required) - `verification-before-completion` - `piped-gate-masks-exit-code` โ€” the same "gate silently didn't gate" family
View on GitHub