How to Use Claude Code Hooks: A Practical Guide with Examples
Claude Code hooks let you run your own shell commands automatically on events like tool use and session stop. Here's how to set them up, with copy-paste examples.
By Ali Hamza
Claude Code hooks are shell commands that run automatically at specific points in a session — before or after a tool runs, when the agent stops, and more. They let you enforce rules, format code, send notifications, or block unwanted actions without asking the model to remember anything. This guide shows exactly how to configure them, with examples you can copy.
What hooks are for
The model is non-deterministic — it might forget to run your formatter or skip a check. Hooks are deterministic: they always fire on the event you bind them to. That makes them perfect for:
- Auto-formatting files after every edit
- Blocking edits to sensitive files (like
.env) - Running tests or linters after changes
- Sending a desktop notification when a long task finishes
- Logging every command the agent runs
Where hooks live
Hooks are configured in your settings JSON — typically
~/.claude/settings.json (global) or .claude/settings.json (per project).
The shape looks like this:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_FILE_PATHS\""
}
]
}
]
}
}This runs Prettier on any file Claude edits or writes — automatically, every time.
The main hook events
| Event | Fires when… | Common use |
|---|---|---|
PreToolUse | Before a tool runs | Block or validate an action |
PostToolUse | After a tool succeeds | Format, lint, run tests |
UserPromptSubmit | You send a message | Inject context, log prompts |
Stop | The agent finishes responding | Notify, summarize |
SessionStart | A session begins | Load environment info |
Example 1: Auto-format after edits
The most popular hook. Add to .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATHS\"" }
]
}
]
}
}Example 2: Block edits to protected files
Use a PreToolUse hook that exits non-zero to block an action:
#!/usr/bin/env bash
# .claude/hooks/protect-env.sh
if echo "$CLAUDE_FILE_PATHS" | grep -q "\.env"; then
echo "Blocked: editing .env is not allowed." >&2
exit 2 # non-zero exit blocks the tool call
fi{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": ".claude/hooks/protect-env.sh" }
]
}
]
}
}Example 3: Notify when a task finishes
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "osascript -e 'display notification \"Claude is done\" with title \"Claude Code\"'" }
]
}
]
}
}(That command is macOS-specific; on Linux use notify-send.)
Tips for reliable hooks
- Keep them fast. Hooks run inline — a slow hook slows every action.
- Exit codes matter. A non-zero exit from a
PreToolUsehook blocks the action; use that to enforce rules. - Test in one project first before promoting a hook to your global settings.
- Log while debugging. Have the hook append to a file so you can see when it fires.
Wrapping up
Hooks turn Claude Code from a smart assistant into a governed one — your formatting, checks, and guardrails run every single time, deterministically. Start with the auto-format hook, then add guardrails as you find rough edges.
Want more? See our other Claude Code guides.
Get new AI coding guides in your inbox
Practical Claude Code tutorials and AI dev tips. No spam, unsubscribe anytime.