Here are ready-to-use hook recipes. Drop them into your .claude/settings.json and adjust paths as needed.
Auto-format with Prettier after every file edit
This PostToolUse hook runs Prettier on whichever file Claude just edited.
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
}]
}
}
Block writes to protected files
This PreToolUse script exits with code when Claude tries to touch .env or package-lock.json. CLAUDE.md can suggest Claude avoid these files. This script makes it impossible.
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED=(".env" "package-lock.json")
for pattern in "${PROTECTED[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH is protected" >&2
exit 2
fi
done
exit 0
Desktop notification when Claude stops
When you're working in another window and Claude finishes, this Stop hook pings you.
# macOS
osascript -e 'display notification "Claude needs your attention" with title "Claude Code"'
# Linux
notify-send 'Claude Code' 'Needs your attention'
Always include this guard at the top of a Stop hook to prevent an infinite loop:
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
Run tests asynchronously after edits
async: true means Claude doesn't wait for the tests before continuing.
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{ "type": "command", "command": ".claude/hooks/run-tests.sh", "async": true, "timeout": 300 }]
}]
}
}
Re-inject context after compaction
A SessionStart hook fires when the session opens and can echo constants back into context.
{
"hooks": {
"SessionStart": [{
"matcher": "compact",
"hooks": [{ "type": "command", "command": "echo 'Use Bun, not npm. Current sprint: auth refactor.'" }]
}]
}
}