You'll finish with: Hooks that block when they break instead of waving things through.
The mistake almost everyone makes, and it's invisible.
You write a hook that depends on a tool being installed. On your machine it is. On a new laptop it isn't. The hook can't run, exits quietly, and allows everything. You believe you're protected. You're not.
When the hook itself breaks
A guardrail that fails open is worse than no guardrail — because you stop watching. No guardrail at least keeps you careful.
This is the part that catches people out, and it is the whole chapter in one sentence: a PreToolUse hook only stops the action if it exits with code 2. Anthropic's docs are explicit that exit 1 — the ordinary Unix "something went wrong" code — is treated as a non-blocking error and the action goes ahead anyway.
So a hook that crashes, or hits a missing command, or returns any failure code that isn't 2, has not protected you. It has logged a complaint while the thing happened.
Your hook has to deliberately say "no" with exit 2. Merely failing is read as "this hook had a bad day, carry on". If you write your own hooks later and remember one thing from this course, make it this.
Both hooks try three interpreters in turn, and only if none of them can read the input do they give up — with exit 2, and a message saying why. They never fail silently and they never fail open.
if [ "$PARSED" -ne 1 ]; then
echo "Blocked: this hook could not read its input, so it cannot tell whether" >&2
echo "the action is safe. It needs a working python3 (or python) on PATH." >&2
exit 2
fi
That block is the difference between a lock and a lock-shaped ornament.
Anyone can check a hook blocks what it should. Almost nobody checks what happens when the hook itself is broken — which is the case that actually bites.
mv protect-paths.sh protect-paths.bakfor PY in python3 python py; do to for PY in nope; do. That's exactly what a machine without Python looks like to your hook.for PY in python3 python py; do and confirm normal edits work again.At step 5 the edit is refused and you can see why — the block message names the missing interpreter. At step 6 editing works normally again. That's a hook that fails closed and tells you, which is the entire point.
If step 5 lets the edit through, your hook is failing open. The usual cause is a guard that treats any non-zero exit as a block — remember only exit 2 stops anything, so a script that ends in exit 1, or crashes, waves the work through. Compare your file against the one above. Don't try to test this by putting exit 9 at the top of the hook: 9 isn't 2, so it won't block, and you'll wrongly conclude your setup is broken.
Two locks on the four limits, both failing closed, both tested against the case that actually matters. Next, most people connect real tools — that's Connectors.