Tech Update / Adventures with Claude Code #11
Hooks – when “please always” isn't enough
Our CLAUDE.md says: “Please run Prettier after every change.” It usually works. Usually.
The problem: CLAUDE.md (part 4) is a request. Claude reads it, understands it and still forgets it now and then, especially in long sessions. For things that must always happen, there are hooks.
A hook is a shell command that Claude Code runs automatically at fixed points. The tool decides, not the model.
⏱️ The most important events
PreToolUse: before Claude writes a file or runs a command. Can block.PostToolUse: right afterwards. Ideal for formatting and linting.Stop: when Claude thinks it's done.Notification: when Claude is waiting for you. Perfect for a desktop notification.
🧹 Example 1: format after every change
In .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
The hook receives the details of the tool call as JSON, and jq pulls out the file name. From now on every file is formatted, without anyone having to remember.
🛑 Example 2: hands off the .env
A PreToolUse hook checks the file name. If the script exits with code 2, the call is blocked and Claude gets the error message back as the reason:
#!/bin/bash
file=$(jq -r '.tool_input.file_path // empty')
if [[ "$file" == *".env"* ]]; then
echo "Access to .env is blocked." >&2
exit 2
fi
exit 0
A word of caution:
Hooks run with your permissions and without asking. So read other people's hooks as critically as you would their shell scripts. Also: a slow PostToolUse hook slows down every single change. Check only the changed file, not the whole project.
My rule of thumb:
- Should Claude know it?
CLAUDE.md. - Must it always happen? Hook.
- Must it never happen? Permissions (part 7) plus a
PreToolUsehook as a second seatbelt.
Deutsch
English