Claude Code Hooks: A Complete Guide with Examples
Hooks let you run shell commands automatically when Claude Code does things — before or after tool calls, on session start, on notifications. They're the escape hatch for enforcing rules that CLAUDE.md alone can't guarantee.
What are hooks?
Hooks are shell commands defined in your Claude Code settings that fire on specific events. They run synchronously — Claude waits for them to finish before continuing.
If a hook exits with a non-zero code, the action is blocked and Claude sees the error output.
Where to define them
Hooks live in your settings file. You can scope them globally or per-project:
- Global:
~/.claude/settings.json - Project:
.claude/settings.json(checked into your repo)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npm run lint --fix $CLAUDE_FILE_PATH"
}
]
}
}
Available hook events
PreToolUse
Fires before Claude executes a tool. Return a non-zero exit code to block it.
Use cases:
- Prevent writes to protected files
- Block dangerous bash commands
- Require confirmation for destructive operations
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "echo $CLAUDE_TOOL_INPUT | grep -q 'rm -rf' && echo 'Blocked: rm -rf' && exit 1 || exit 0"
}
]
}
}
PostToolUse
Fires after a tool completes successfully. This is the most commonly used hook.
Use cases:
- Auto-format files after edits
- Run linting after code changes
- Trigger rebuilds
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "prettier --write $CLAUDE_FILE_PATH 2>/dev/null; exit 0"
}
]
}
}
Notification
Fires when Claude sends a notification (usually when it's done with a long task and waiting for input).
{
"hooks": {
"Notification": [
{
"command": "osascript -e 'display notification \"Claude is waiting\" with title \"Claude Code\"'"
}
]
}
}
SessionStart
Fires when a new Claude Code session begins.
{
"hooks": {
"SessionStart": [
{
"command": "echo 'Session started at $(date)' >> ~/.claude/session.log"
}
]
}
}
Environment variables available in hooks
When your hook runs, Claude Code sets these environment variables:
| Variable | Description |
|---|---|
CLAUDE_FILE_PATH |
The file being read/written/edited |
CLAUDE_TOOL_INPUT |
JSON string of the tool's input parameters |
CLAUDE_TOOL_NAME |
Name of the tool (Write, Edit, Bash, etc.) |
The matcher field
The matcher field filters which tools trigger the hook. It's a regex pattern matched against the tool name.
"Write"— only Write tool"Write|Edit"— Write or Edit"Bash"— only Bash tool- Omit
matcherentirely to match all tools
Practical examples
Auto-format on save
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null; exit 0"
}
]
}
}
The ; exit 0 ensures the hook never fails — you don't want formatting errors to block Claude.
Prevent editing lockfiles
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"command": "echo $CLAUDE_FILE_PATH | grep -qE '(package-lock|yarn\\.lock|pnpm-lock)' && echo 'Do not edit lockfiles directly. Use npm/yarn/pnpm install instead.' && exit 1 || exit 0"
}
]
}
}
Run tests after code changes
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "echo $CLAUDE_FILE_PATH | grep -qE '\\.(ts|tsx)$' && npm test -- --related $CLAUDE_FILE_PATH 2>/dev/null; exit 0"
}
]
}
}
Desktop notification when Claude finishes
On macOS:
{
"hooks": {
"Notification": [
{
"command": "osascript -e 'display notification \"Claude needs your attention\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
}
Tips
Always exit 0 in PostToolUse hooks unless you genuinely want to signal failure. A formatting error shouldn't stop Claude from working.
Keep hooks fast. They run synchronously. A slow hook means Claude waits on every single tool call, eating into your token budget as context accumulates. If you need to run something expensive, background it with &.
Test hooks manually first. Run the command yourself with sample values before putting it in settings. A broken hook will confuse both you and Claude.
Use PreToolUse sparingly. Blocking hooks that are too aggressive will make Claude feel broken — it'll keep trying and failing. Reserve them for genuinely dangerous operations.
Hooks are not CLAUDE.md. Use CLAUDE.md for conventions and guidelines. Use hooks for hard enforcement — things that should never happen regardless of what Claude decides. For reusable workflows that don't need enforcement, consider skills instead.