How to prove your Claude Code hooks actually fire (Chapter 4)

Installing a guard and trusting it are different acts. --verify runs each hook against a sample input and reports what happened.

1 npx github:yurukusa/cc-safe-setup --verify

Run on a project where nothing is installed, it says so, and it fails:

 1   cc-safe-setup --verify
 2   Testing each hook with sample inputs...
 3 
 4   ✗ destructive-guard: blocks rm -rf / (not installed)
 5   ✗ destructive-guard: allows safe commands (not installed)
 6   ✗ branch-guard: blocks push to main (not installed)
 7   ✗ branch-guard: allows push to feature (not installed)
 8   ✗ destructive-guard: blocks git checkout --force (not installed)
 9   ✗ secret-guard: blocks git add .env (not installed)
10   ✗ secret-guard: allows git add safe files (not installed)
11   ✗ api-error-alert: ignores normal stops (not installed)
12 
13   ... twelve more, then:
14 
15   0/20 tests passed.
16   20 failures. Run npx github:yurukusa/cc-safe-setup to reinstall.

Trimmed for print: twenty tests run; the first eight are shown. Nothing is reworded — an earlier draft of this chapter paraphrased two of the test names, because the credential guard on the author’s own machine refused the command that was writing the manuscript when the literal names appeared in it. That was the wrong trade, and the names are now as they come back.

The exit code is the part to care about. It is 1, not 0 — the summary line above is what you read, and the exit status is what CI reads. A check that returns success no matter what it finds tells you nothing.

Look at the pairs. “blocks rm -rf /” and “allows safe commands” are both tests. A guard that blocks everything passes the first and fails the second; a guard that blocks nothing does the reverse. If you write your own hooks, copy that shape. Always test the allow case, or you will ship something that stops your own work and call it safety.

Run --verify again after installing, and again after any change to your hooks. The failure mode that costs you is not “I had no guard”. It is “I had a guard, and it had quietly stopped matching.”