Your first blocking hook

You'll finish with: A working hook that blocks writes to secrets.

Time to build one. This blocks any attempt to write to files holding secrets — passwords, keys, API tokens.

How a hook answers

Allow
exit 0
nothing happens, work continues
Block
exit 2
action stopped, Claude told why

Where it goes

smith-plumbing/
├── CLAUDE.md
└── .claude/
    ├── settings.json
    └── hooks/protect-paths.sh
Plain English

The dot in .claude makes the folder hidden. Mac: press Cmd + Shift + . in Finder to see it. Windows: tick "Hidden items" in the View menu.

How it decides

Claude sends the hook a description of what it's about to do. The script looks at the filename and exits with a number: 0 means allow, 2 means block and tell Claude why.

Plain English

JSON is a way of writing settings that programs can read. It's fussy about punctuation: every { needs its }, and a single missing comma or bracket makes the whole file unreadable — at which point your hook silently never runs. If something isn't working, paste the file into a free "JSON validator" in your browser; it will point at the exact character.

Plain English

chmod +x means "let this file be run as a program". Mac and Linux need it before a script will do anything. On Windows you don't need it and it won't work — Windows decides differently. If you're on Windows and using Git Bash or WSL, run it there; in plain PowerShell, skip it.

Do this now
  1. Create the folders: .claude/hooks/
  2. Save the script below as protect-paths.sh inside it.
  3. Make it runnable — in Terminal, from your business folder: chmod +x .claude/hooks/*.sh
  4. Save the settings block as .claude/settings.json
  5. Restart Claude.
#!/bin/bash
# Blocks writes to secrets and your offers file. FAILS CLOSED.
INPUT=$(cat)

VALUE=""
PARSED=0
for PY in python3 python py; do
  command -v "$PY" >/dev/null 2>&1 || continue
  OUT=$(printf '%s' "$INPUT" | "$PY" -c "
import sys,json
try: print(json.load(sys.stdin).get('tool_input',{}).get('file_path','') or '')
except Exception: sys.exit(9)" 2>/dev/null) || continue
  VALUE="$OUT"
  PARSED=1
  break
done

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
  echo "Install Python, then try again. Blocking is deliberate — see the Gates course." >&2
  exit 2
fi

FILE="$VALUE"
[ -z "$FILE" ] && exit 0
case "$FILE" in
  *.env|*.env.*|*/secrets/*|*credentials*|*.pem|*.key)
    echo "Blocked: $FILE holds secrets." >&2; exit 2 ;;
  */business/offers.md|business/offers.md)
    echo "Blocked: pricing is owner-edit only." >&2; exit 2 ;;
esac
exit 0
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-paths.sh"
      }]
    }]
  }
}
How to know it worked

Ask Claude to edit a file called .env. It should refuse and say the hook blocked it.

If the edit goes through: check settings.json is inside .claude/, and that you ran the chmod line.

All courses · Start here · Privacy