What are hooks?
Hooks let you intercept and react to Claude Code lifecycle events: before running a tool, after doing so, when Claude finishes a response, and so on.
Common use cases:
- Automatically format code after each edit.
- Log all Claude operations.
- Block dangerous operations with custom logic.
- Send notifications when Claude finishes a long task.
- Run tests automatically after each change.
Hook types
Configuring hooks
Hooks are configured in .claude/settings.json (project) or ~/.claude/settings.json (global):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "prettier --write $CLAUDE_FILE_PATH"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification "Claude finished" with title "Claude Code"'"
}
]
}
]
}
}Environment variables available in hooks
The matcher
The matcher field is a regular expression matched against the tool name. If not specified, the hook applies to all tools.
# TypeScript file editing only "matcher": "Edit" # Bash and editing tools "matcher": "Bash|Edit|Write" # Any read tool "matcher": "^Read"
Command-type hooks
The most common type. Runs a shell command. The hook can read event information via environment variables or via stdin (JSON):
{
"type": "command",
"command": "bash /path/to/my-hook.sh",
"timeout": 30
}Example: hook that reads event data from stdin
#!/bin/bash # my-hook.sh — receives the full event as JSON via stdin EVENT=$(cat) TOOL=$(echo $EVENT | jq -r '.tool_name') FILE=$(echo $EVENT | jq -r '.tool_input.path // ""') echo "Tool: $TOOL, File: $FILE" >> ~/.claude/hook.log
Blocking operations with PreToolUse
A PreToolUse hook can return a non-zero exit code to block the operation:
#!/bin/bash # Block rm -rf TOOL=$(cat | jq -r '.tool_name') CMD=$(cat | jq -r '.tool_input.command // ""') if [[ "$CMD" == *"rm -rf"* ]]; then echo "BLOCKED: rm -rf not allowed" exit 1 # exit 1 = block the operation fi exit 0 # exit 0 = allow
Configure it like this in settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "bash ~/.claude/hooks/block-rm.sh"
}]
}]
}
}Practical examples
Format with Prettier after editing
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "npx prettier --write "$CLAUDE_FILE_PATH" 2>/dev/null || true"
}]
}]
}
}Run tests after changes
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "npm test --watchAll=false 2>&1 | tail -20"
}]
}]
}
}Log all operations
{
"hooks": {
"PostToolUse": [{
"hooks": [{
"type": "command",
"command": "echo "$(date) - $CLAUDE_TOOL_NAME: $CLAUDE_FILE_PATH" >> ~/.claude/audit.log"
}]
}]
}
}Managing hooks from the CLI
# View configured hooks (inside Claude Code) /hooks # Or open settings.json directly: claude config