Hooks let you run your own scripts during a Claude Code session. The three main ones fire at different moments, and picking the wrong one is a common source of bugs.
The three hooks aren't interchangeable. They answer three different questions:
Getting this wrong means your safety check runs after the damage is done, or your cleanup script fires five times instead of once.
You configure hooks in your project's .claude/settings.json. Each hook points to a script and specifies which tool (or all tools) it applies to.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "python hooks/check_command.py" }
]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{ "type": "command", "command": "python hooks/log_write.py" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "python hooks/session_summary.py" }
]
}
]
}
}
PreToolUse receives details about the tool call before it runs. If your script exits with a non-zero code, Claude Code blocks the action entirely. This is where you put guardrails — prevent writes to certain folders, block dangerous shell commands, require confirmation.
PostToolUse fires after the tool has already run. You get both the input and the output. Good for logging, formatting, or triggering downstream actions (push a notification, update a log file). You cannot undo what Claude just did from here.
Stop fires once, when Claude decides the task is complete and stops. It has no tool context — Claude is done. Use it for session-level cleanup: write a summary to Obsidian, commit work in progress, ping a webhook.
Use PreToolUse when you want to prevent something — it's your only chance to stop a tool call before it happens. Use PostToolUse when you want to react to something. Use Stop for once-per-session bookkeeping.
Don't use PostToolUse as a safety net for things that should have been caught in PreToolUse. By the time PostToolUse runs, the file is written and the command has executed.
Add a minimal Stop hook today. Create hooks/session_end.py that appends a timestamped line to a log file, then register it under Stop in .claude/settings.json. Run a short Claude Code session and confirm the line appears when Claude finishes. Once that works, you'll have the wiring in place for anything more ambitious.
Don't miss what's next. Subscribe to My Claude Daily Learning: