Hooks Implementation — Automate with Lifecycle Hooks
By configuring hooks (Hooks), you can automatically execute any shell commands at the timing when Claude Code runs tools or returns responses. This page covers the 4 hook event types, environment variables, the meaning of exit codes, and how to configure them in .claude/settings.json — in order.
What Are Hooks?
A hook is a mechanism that allows shell commands to run at specific timings when Claude Code calls tools or generates responses. Hooks are configured in the hooks key of .claude/settings.json.
The main uses of hooks are as follows.
- Logging: Record which tools were called and when to a file
- Validation: Block dangerous commands from being executed
- Auto-formatting: Automatically run a linter after a file is saved
- Notification: Notify via system notification when Claude completes its work
The 4 Hook Event Types
Claude Code has 4 hook event types.
| Event | Timing | Main Use |
|---|---|---|
PreToolUse | Before tool execution | Validation, blocking |
PostToolUse | After tool execution | Logging, notification, post-processing |
UserPromptSubmit | When the user inputs | Prompt transformation, pre-processing |
Stop | When the response ends | Completion notification, post-processing |
PreToolUse is called before tool execution, so it functions as a gatekeeper that can block the use of specific tools. PostToolUse is called after tool execution, so it is suitable for adding side effects (log writing, notifications, etc.).
The Structure of the Configuration File
Hooks are configured in the hooks key of .claude/settings.json.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'Tool: $CLAUDE_TOOL_NAME'"
}
]
}
]
}
}The structure of the configuration is as follows.
hooks: The root key for hook configurationPreToolUse/PostToolUse/UserPromptSubmit/Stop: Event namematcher: Which tool name to apply the hook to. Writing"Bash"means it only runs when the Bash tool is used. When omitted, it applies to all toolstype: The type of hook. Currently only"command"is supportedcommand: The shell command to execute
Environment Variables
During hook execution, Claude Code provides the following environment variables.
| Variable | Content | Available Events |
|---|---|---|
$CLAUDE_TOOL_NAME | Name of the called tool (e.g., Bash, Read) | PreToolUse, PostToolUse |
$CLAUDE_TOOL_INPUT | Input to the tool (JSON format) | PreToolUse, PostToolUse |
$CLAUDE_TOOL_RESULT | Tool execution result (JSON format) | PostToolUse only |
Since $CLAUDE_TOOL_INPUT is a JSON string, you can use the jq command to extract specific fields.
# Example of extracting the command string passed to the Bash tool
echo "$CLAUDE_TOOL_INPUT" | jq -r '.command'The Meaning of Exit Codes
The behavior of Claude Code changes depending on the exit code of the hook script.
| Exit code | Meaning |
|---|---|
0 | Success. Continue tool execution |
1 | Error. If PreToolUse, blocks tool execution |
2 | Non-blocking error. Display an error message but continue execution |
If a PreToolUse hook returns exit 1, Claude aborts that tool’s execution. Even if a PostToolUse hook returns exit 1, the tool operations already executed cannot be undone.
Best Practices
Keep Hooks Fast
If hooks perform heavy processing (network communication, large file processing, etc.), Claude’s response slows down. Run heavy processing in the background.
# Example of running in the background (add & to background it)
some-heavy-command "$CLAUDE_TOOL_NAME" &Make Errors Explicit
When a hook blocks, always output an error message before exit 1. Without a message, the user won’t know why it was blocked.
echo 'ERROR: npm run build requires explicit user approval.'
exit 1Design PreToolUse Carefully
If too many blocking hooks are set, Claude Code becomes hard to use. Narrow down the blocking conditions specifically to avoid accidentally blocking necessary operations.
Keep Logs
Recording tool usage logs with a PostToolUse hook to a file is useful for auditing and debugging.
echo "$(date): $CLAUDE_TOOL_NAME" >> /tmp/claude-tool-log.txtWrite Hook Scripts to Be Unit-Testable
Rather than writing long commands directly in the command field, it is easier to test and maintain if you extract them as shell scripts in a separate file and call them.
Hands-on Exercise
Step 1: Check the Current Hook Configuration
cat .claude/settings.jsonCheck the presence of the hooks key and the current configuration contents.
✅ Verify: If the file contents are displayed, the read was successful. If the
hookskey is absent, add it in the next step.
Step 2: Add a PreToolUse Hook to Prevent Accidentally Running npm run build
Add the following configuration to the hooks section of .claude/settings.json.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'npm run build'; then echo 'ERROR: npm run build requires explicit user approval.'; exit 1; fi"
}
]
}
]
}
}This hook blocks the Bash tool input and displays an error message if it contains npm run build.
✅ Verify: After saving
.claude/settings.json, confirm withcat .claude/settings.jsonthat the configuration has been correctly recorded.
Step 3: Confirm the Hook Blocks Correctly
In a Claude Code session, make the following request.
Please run npm run buildWhen Claude tries to run npm run build with the Bash tool, confirm that the hook returns exit 1 and blocks it.
✅ Verify: If Claude’s response contains something to the effect that “npm run build was blocked” or “explicit user approval is required,” the hook is working correctly.
Step 4: Add a Stop Hook to Notify macOS When Work Is Complete
Add a Stop hook that sends a macOS notification when Claude finishes a response.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'npm run build'; then echo 'ERROR: npm run build requires explicit user approval.'; exit 1; fi"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude has finished working\" with title \"Claude Code\"'"
}
]
}
]
}
}✅ Verify: If “Claude has finished working” appears in the macOS Notification Center when Claude returns the next response, it is a success. On non-macOS environments, replace
osascriptwith the appropriate command.
Summary
- Hooks are a mechanism to run shell commands at specific timings in the Claude Code lifecycle
- The 4 event types are
PreToolUse(before tool),PostToolUse(after tool),UserPromptSubmit(on input), andStop(on end) - During hook execution,
$CLAUDE_TOOL_NAME,$CLAUDE_TOOL_INPUT, and$CLAUDE_TOOL_RESULTare available - Returning
exit 1inPreToolUsecan block tool execution - Configure hooks in the
hookskey of.claude/settings.json, designing them to be lightweight, explicit, and testable
Next Steps
- MCP Plugins Integration — Learn about configuring MCP plugins that integrate external tools with Claude Code
- Harness Testing and Validation — Move on to systematically verifying that hooks function as intended
Frequently Asked Questions
Q: How does Claude behave when blocked by a PreToolUse hook?
A: When the hook returns exit 1, Claude does not execute that tool and receives the error message written to stdout as context. After that, Claude tries a different approach or reports to the user based on the content of the error.
Q: If a PostToolUse hook returns exit 1, can the tool’s execution result be cancelled?
A: It cannot be cancelled. PostToolUse is called after tool execution, so operations that have already completed (such as file writes) cannot be undone. Perform pre-execution validation with PreToolUse.
Q: What happens if matcher is omitted?
A: The hook executes for all tool calls. Not just Bash, but also Read, Write, and Glob become targets, so if the hook performs heavy processing, the response may slow down significantly. If you want to target only specific tools, explicitly specify matcher.
Q: Is it necessary to restart Claude Code after changing the hook configuration?
A: Changes to .claude/settings.json are read across sessions. To reflect changes in the current session, restart Claude Code or start a new session.
Q: What command can be used instead of osascript on Windows?
A: On Windows, you can use powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('Claude has finished working')". On Linux, notify-send "Claude has finished working" works (requires libnotify).
See the references for the external specifications and background sources used on this page.[1][2]
References
- Anthropic, Claude Code documentation
- Anthropic, Claude API documentation
For the latest releases and updates, check the official website and official documentation.