The silent gap (and why it mattered)
if condition to guard a sensitive path — block a Read of your .env, an Edit anywhere in src/**, a Read of ~/.ssh/**. The catch: before version 2.1.176, those documented path patterns silently failed to match. The hook never fired. No block, no error — just a guard quietly doing nothing. The Claude Code v2.1.176 changelog states it directly: 'Fixed hook if conditions for Read/Edit/Write tool paths: documented patterns like Edit(src/**), Read(~/.ssh/**), and Read(.env) now match correctly.'Step 1 — Upgrade to 2.1.176 or later
- Update:
claude update(or reinstall via your package manager / npm, however you installed it). - Confirm:
claude --versionand check it reads 2.1.176 or higher. - Only after you're on >= 2.1.176 is it worth wiring up (or trusting) a path-scoped guard hook.
Step 2 — The copy-paste protective hook
.claude/settings.json (or your user-level settings) and tighten the paths to your repo:{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"if": "Read(.env) || Read(.env.*) || Read(~/.ssh/**) || Read(**/secrets/**)",
"command": "echo 'BLOCKED: sensitive read' >&2; exit 2"
},
{
"matcher": "Edit|Write",
"if": "Edit(.env) || Write(.env) || Edit(~/.ssh/**) || Edit(**/*.pem)",
"command": "echo 'BLOCKED: sensitive write' >&2; exit 2"
}
]
}
}exit 2is the signal a PreToolUse hook uses to BLOCK the tool call (a non-zero deny). A zero exit would allow it.- Match Read separately from Edit/Write — they're different tool names, and you usually want different paths blocked for each.
- Tighten the globs to YOUR layout: add
**/config/credentials*,**/*.key, your terraform state, etc.
if-condition syntax and settings fields are version-sensitive, so verify against the Claude Code hooks documentation for your version before you rely on it. And after editing settings.json, restart Claude Code (or start a new session) so the hook is loaded — hooks are read at startup, not hot-reloaded.Step 3 — The 30-second test that proves it fires
- In a session on >= 2.1.176, ask Claude plainly: 'Read the contents of .env' (a path your hook guards).
- Watch for the block: the tool call should be DENIED and your hook's message ('BLOCKED: sensitive read') should surface — the file contents must NOT come back.
- Repeat for an Edit/Write on a guarded path (e.g. 'edit ~/.ssh/config') and confirm that's denied too.
- If it does NOT block: re-check your version (>= 2.1.176), confirm the hook is in the settings file Claude actually loaded, and that the glob matches the real path you triggered.
Bonus 30-second check — Remote Control silent model switch
- If you use Remote Control (web/mobile) to attach to sessions, upgrade to >= 2.1.176 so the connect no longer silently swaps your model.
- After connecting from web/mobile, glance at the active model indicator and confirm it's the one you meant to run.
- This is a 'while you're in there' check — not the headline, but a free win once you've already upgraded.
Make it a habit — the principle
- Every time you add or change a security hook, run the 30-second deny test before you trust it.
- Keep the test trivially cheap (one prompt per guarded path) so you'll actually do it.
- Treat 'it didn't error' as NOT the same as 'it blocked' — silent success is the trap this whole episode is about.
Get the next drop
New AI build guides + the occasional bonus template. No spam, unsubscribe anytime.
By submitting you agree to our Privacy Policy & Terms. Unsubscribe anytime.
Frequently asked questions
Does this mean my secrets were leaked?
Which version has the fix?
How does a PreToolUse hook block a call?
exit 2) to deny the tool call before it runs. A zero exit allows it. That's why the 30-second test matters — you want to SEE the deny.