Recipe 1: Auto-format every edit
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "f=$(jq -r '.tool_input.file_path') && npx prettier --write --ignore-unknown \"$f\"" }
]
}
]
}
}Every file Claude touches is formatted, every time. No more "please run prettier" in CLAUDE.md. Swap in black, gofmt, rustfmt or ruff format for your stack.
Recipe 2: Protect sensitive files
A small script checks the target path and blocks matches:
#!/usr/bin/env bash
file=$(jq -r '.tool_input.file_path // empty')
PROTECTED=(".env" "package-lock.json" ".git/" "migrations/applied/")
for p in "${PROTECTED[@]}"; do
if [[ "$file" == *"$p"* ]]; then
echo "Blocked: $file is protected. Explain what you need and I'll make the change myself." >&2
exit 2
fi
done
exit 0chmod +x .claude/hooks/protect-files.sh{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" }]
}
]
}
}A hook on Edit|Write doesn't stop echo "x" > .env through Bash. For sensitive files, combine the hook with permissions.deny rules like Edit(./.env) and Read(./.env), plus sandboxing.
Recipe 3: Notify me when Claude needs input
Put this in your user settings so it works in every project:
{
"hooks": {
"Notification": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "notify-send 'Claude Code' 'Claude Code needs your attention'" }] }
]
}
}On macOS, use osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'. Start a long task, go get coffee, and get pinged when it's your turn.
Recipe 4: Fresh context at session start
SessionStart hooks can print text that's added to Claude's context:
{
"hooks": {
"SessionStart": [
{ "matcher": "startup", "hooks": [{ "type": "command", "command": "echo \"Open PRs:\"; gh pr list --limit 5" }] }
]
}
}The compact matcher fires after compaction, which makes it a neat way to re-inject critical context that a summary might drop.
Check your understanding
Which event should run a formatter?
Why reference scripts with "$CLAUDE_PROJECT_DIR"?
Why isn't an Edit|Write hook alone enough to protect .env?
Where should a personal notification hook live?