Claude Code Hooks Complete Guide (March 2026 Edition)¶
What this guide gives you that AI search cannot
AI assistants can explain what hooks are. This guide gives you copy-paste configs that work, tested against Claude Code v2.x in production:
- 5 ready-to-use
.claude/settings.jsonsnippets below -- paste one and it works in 10 seconds - Behavior differences across 21 lifecycle events (which exit codes block, which don't)
- Real debugging patterns for the "hook doesn't fire" and "infinite Stop loop" problems
If you just need the concept, ask an AI. If you need it to actually run, keep reading.
5 Copy-Paste Hooks to Start With¶
Paste any of these into .claude/settings.json. Each one is self-contained.
1. Auto-format every file Claude edits (most popular):
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit|MultiEdit",
"hooks": [{ "type": "command", "command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\"" }]
}]
}
}
2. Block dangerous commands (rm -rf, DROP TABLE):
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf|DROP TABLE' && exit 2 || exit 0" }]
}]
}
}
3. Inject git branch context on session start:
{
"hooks": {
"SessionStart": [{
"hooks": [{ "type": "command", "command": "echo '{\"additionalContext\": \"Branch: '$(git branch --show-current)'\"}'" }]
}]
}
}
4. Force tests to pass before Claude stops:
{
"hooks": {
"Stop": [{
"hooks": [{ "type": "command", "command": "INPUT=$(cat); [ \"$(echo $INPUT | jq -r '.stop_hook_active')\" = 'true' ] && exit 0; npm test || exit 2" }]
}]
}
}
5. Log every completed session:
{
"hooks": {
"Stop": [{
"hooks": [{ "type": "command", "command": "echo \"$(date): session completed\" >> ~/claude-work.log", "async": true }]
}]
}
}
How to use
Copy the JSON, paste into .claude/settings.json (create the file if it doesn't exist), and restart Claude Code. That's it -- no npm install, no build step.
Why Hooks Instead of Prompts?¶
Every time Claude Code writes a file, you want Prettier to run. Every time it tries rm -rf /, you want it blocked. You could ask in the prompt -- but prompts are requests, not guarantees. Hooks make it deterministic.
The rest of this guide covers all 21 events, 4 handler types, async execution, and JSON-based advanced control. Use the table of contents to jump to what you need.
Key Changes (March 2026 Update)
Events expanded from 14 to 21 (InstructionsLoaded, ConfigChange, WorktreeCreate/Remove, PostCompact, Elicitation/ElicitationResult added), HTTP hook type added, /hooks menu is now read-only, disableAllHooks for bulk disable.
What are Claude Code Hooks?¶
Hooks inject deterministic processing into Claude Code's lifecycle. Unlike prompt instructions, which are requests that Claude may interpret flexibly, hooks execute the same way every time.
Claude Code execution → Specific event fires → Matcher evaluation → Hook execution (command / http / prompt / agent)
Hook Events (All 21 Types)¶
All 21 lifecycle events defined in the official documentation.
SessionStart
Fires on session start (startup / resume / clear / compact) Example: Development context injection, environment variable persistence
SessionEnd
Fires on session end Example: Cleanup, session statistics logging
UserPromptSubmit
Fires when user submits a prompt Example: Prompt validation, context augmentation
PreToolUse
Fires before tool execution Example: Blocking dangerous commands, rewriting tool input
PermissionRequest
Fires when permission dialog appears Example: Auto-approving safe commands
PostToolUse
Fires after successful tool execution Example: Auto lint/format after file save
PostToolUseFailure
Fires after tool execution failure Example: Failure logging, retry decision
Notification
Fires on notification (permissionprompt / idleprompt / authsuccess / elicitationdialog) Example: Desktop notification, Slack notification
Stop
Fires when Claude completes response Example: Auto git commit, task completion logging
SubagentStart
Fires when a subagent starts Example: DB connection setup, environment preparation for specific agents
SubagentStop
Fires when a subagent completes Example: Forcing task completion, result logging
PreCompact
Fires before context compaction Example: Backup of conversation transcript
PostCompact
Fires after context compaction completes Example: Compaction summary logging, context re-injection
TeammateIdle
Fires just before an Agent Teams teammate becomes idle Example: Reassigning tasks to idle agents
TaskCompleted
Fires when a task is marked complete Example: Completion notification, triggering next task
InstructionsLoaded
Fires when CLAUDE.md or
.claude/rules/*.mdis loaded into context Example: Configuration file monitoring (observe only, no control)ConfigChange
Fires when a configuration file changes during a session Example: Audit logging, blocking unauthorized changes
WorktreeCreate
Fires when a worktree is created via
--worktreeorisolation: "worktree"Example: Custom worktree creation logicWorktreeRemove
Fires when a worktree is removed Example: Cleanup processing
Elicitation
Fires when an MCP server requests user input Example: Auto-respond to input requests, decline requests
ElicitationResult
Fires after user responds to an MCP elicitation Example: Response validation, logging
Hook Handler Types (4 Types)¶
Four handler types are available.
| Type | Description | Use Case | Supported Events |
|---|---|---|---|
command | Execute shell commands | lint, git operations, notifications, backups | All events |
http | POST event data to an HTTP endpoint | External service integration, shared audit logging | All events |
prompt | Single-turn LLM evaluation | Context-dependent allow/deny decisions | PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, Stop, SubagentStop, TaskCompleted, etc. |
agent | Multi-turn agent with tool access | Complex decisions based on actual file state | Same as prompt-supported events |
// command type (for deterministic rules)
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
// http type (for external service integration)
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"headers": { "Authorization": "Bearer $MY_TOKEN" },
"allowedEnvVars": ["MY_TOKEN"]
}
// prompt type (when LLM judgment is needed)
{
"type": "prompt",
"prompt": "Evaluate whether this Bash command could affect the production environment: $ARGUMENTS"
}
// agent type (when codebase state verification is needed)
{
"type": "agent",
"prompt": "Check whether tests exist for the changed files"
}
Choosing the Right Type
- command: Simple, deterministic rules (format, lint, git operations) → Always prefer command
- http: External service integration, shared audit/validation → Same control as command
- prompt: When judgment can be made from hook input data alone (e.g., evaluating command risk)
- agent: When actual codebase state needs to be verified (e.g., checking test existence)
Async Hooks — Added January 2026¶
Async hooks that run in the background without blocking have been added.
{
"type": "command",
"command": "node backup-script.js",
"async": true,
"timeout": 30
}
| Sync Hooks (Default) | Async Hooks (async: true) | |
|---|---|---|
| Claude's behavior | Waits for hook completion | Continues immediately without waiting |
| Block control | Can block with exit code 2 | Cannot block |
| Best for | Security checks, permission decisions | Logging, backups, sending notifications |
| Not suitable for | Time-consuming operations | Operations where Claude needs the result |
Exit Code Control System¶
You can control Claude's behavior through hook exit codes.
Exit 0 → Success. Parse JSON output from stdout
Exit 2 → Blocking error. stderr is fed back to Claude
Other → Non-blocking error. stderr shown in verbose mode, execution continues
Exit 2 behavior varies by event:
| Event | Effect of Exit 2 |
|---|---|
| PreToolUse | Blocks the tool call |
| UserPromptSubmit | Rejects the prompt |
| PermissionRequest | Denies permission |
| Stop / SubagentStop | Blocks stopping (forces continued work) |
| TeammateIdle | Prevents teammate from going idle |
| TaskCompleted | Prevents task completion marking |
| ConfigChange | Blocks the configuration change |
| Elicitation | Declines the MCP input request |
| ElicitationResult | Blocks the response (changes to decline) |
| SessionStart / PreCompact | Only displays stderr (cannot block) |
| PostToolUse / Notification | Only displays stderr (already executed) |
Advanced Control via JSON Structured Output¶
When exiting with code 0, you can output JSON to stdout for fine-grained control.
PreToolUse Permission Decisions¶
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Safe read operation",
"updatedInput": {
"command": "modified-command"
},
"additionalContext": "Additional context for Claude"
}
}
"allow": Bypass permission system and allow"deny": Block the tool and feed back the reason to Claude
PermissionRequest Auto-Response¶
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
Stop/SubagentStop Completion Enforcement¶
{
"decision": "block",
"reason": "Tests are failing. Please fix them before completing."
}
Configuration File Locations¶
| Location | Scope | Shareable |
|---|---|---|
~/.claude/settings.json | All projects | No (local only) |
.claude/settings.json | Single project | Yes (can be committed) |
.claude/settings.local.json | Single project | No (gitignored) |
| Managed policy settings | Organization-wide | Yes (admin-controlled) |
Plugin hooks/hooks.json | When plugin enabled | Yes (bundled) |
| Skill/agent frontmatter | While component active | Yes (defined in file) |
Bulk Disable All Hooks
Add "disableAllHooks": true to your settings file to disable all hooks at once.
Configuration Examples¶
1. View Hooks with /hooks CLI Menu¶
# Inside Claude Code CLI
/hooks
# → Event list with hook counts is displayed
# → Select event → View configured hook details
/hooks Menu is Read-Only
To add, modify, or remove hooks, edit your settings JSON directly or ask Claude to make the change.
2. Basic JSON Configuration¶
The auto-format example shown in the introduction is the simplest pattern. You can combine multiple events in one config:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf|DROP TABLE' && exit 2 || exit 0"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "echo 'Work complete!' >> ~/work.log"
}
]
}
]
}
}
3. Context Injection with SessionStart¶
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '{\"additionalContext\": \"Current branch: '$(git branch --show-current)'\"}'"
}
]
}
]
}
}
Stdout output from SessionStart and UserPromptSubmit is added as Claude's context.
4. Environment Variable Persistence (SessionStart Only)¶
With SessionStart hooks, you can persist environment variables across the entire session using CLAUDE_ENV_FILE.
#!/bin/bash
# setup-env.sh - Called from SessionStart hook
echo "PROJECT_ROOT=$(git rev-parse --show-toplevel)" >> "$CLAUDE_ENV_FILE"
echo "NODE_ENV=development" >> "$CLAUDE_ENV_FILE"
5. Hook Definitions in Skill/Agent Frontmatter¶
---
name: code-reviewer
description: Auto-lint review of code changes
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate-command.sh $TOOL_INPUT"
PostToolUse:
- matcher: "Edit|Write"
hooks:
- type: command
command: "./scripts/run-linter.sh"
---
You are an expert code review agent.
Stop Hooks in Frontmatter
Stop hooks defined in skill/agent frontmatter are automatically converted to SubagentStop events.
Use Cases¶
Auto Format/Lint
Auto-format after file changes with PostToolUse. Specify
Write|Edit|MultiEditin matcherSecurity Blocking
Block dangerous Bash commands (
rm -rf, etc.) with exit code 2 in PreToolUseDesktop Notifications
Send OS notifications on idle or permission requests via Notification
Safe Command Auto-Approval
Auto-allow
npm run lintetc. via PermissionRequestTest Enforcement
Use Stop with exit code 2 to enforce "don't complete until tests pass"
Auto Git Management
Integrate with GitButler via PostToolUse for automatic branch isolation per session
Pre-Compaction Backup
Auto-save transcripts before context compaction with PreCompact
Agent Teams Integration
Manage subagent lifecycle with SubagentStart/Stop and TeammateIdle
Session Initialization
Auto-execute dev environment setup, dependency installation, DB initialization with SessionStart
LLM-Judged Hooks
Delegate context-dependent decisions (production impact assessment, etc.) to LLM with prompt/agent types
Practical Pattern: GitButler Integration¶
When running multiple Claude Code sessions simultaneously, GitButler hooks can automatically isolate branches per session.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{ "type": "command", "command": "but claude pre-tool" }
]
}
],
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{ "type": "command", "command": "but claude post-tool" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "but claude stop" }
]
}
]
}
}
Debugging and Troubleshooting¶
Testing Hooks¶
# Test by piping sample JSON to stdin
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $? # Check exit code
Preventing Stop Hook Infinite Loops¶
When a Stop hook returns exit 2, Claude continues working, which can cause infinite loops. Check the stop_hook_active field to prevent this:
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Allow stopping on subsequent invocations
fi
# ... hook logic
Common Issues¶
| Symptom | Cause | Solution |
|---|---|---|
| Hook doesn't execute | Not shown in /hooks | Restart session, check config file syntax |
| "command not found" | Relative path issue | Use absolute paths with $CLAUDE_PROJECT_DIR |
| "jq: command not found" | jq not installed | Install jq, or use Python/Node.js alternative |
| PermissionRequest doesn't fire | Non-interactive mode | Use PreToolUse when using -p flag |
| Can't block with async hook | async: true limitation | Use sync hooks for security checks |
| JSON validation failed | Shell profile echo pollutes JSON output | Add if [[ $- == *i* ]] guard in ~/.zshrc |
| Stop hook runs forever | stop_hook_active not checked | Implement the infinite loop prevention pattern above |
| Settings changes not reflected | File edited mid-session | Check /hooks or restart session |
Using Verbose Mode¶
# Toggle verbose mode with Ctrl+O
# → stdout/stderr from PreToolUse/PostToolUse etc. will be displayed
Version Changelog¶
| Item | Previous (2025) | February 2026 | Current (March 2026) |
|---|---|---|---|
| Number of events | 7 | 14 | 21 |
| Hook handler types | command only | command / prompt / agent | command / http / prompt / agent |
| Async execution | None | async: true support | Same |
| JSON output control | None | permissionDecision, etc. | Same |
| Configuration method | Manual JSON only | /hooks CLI menu + JSON | /hooks (read-only) + JSON + frontmatter |
| Environment variable persistence | None | CLAUDE_ENV_FILE | Same |
| Plugin support | None | hooks/hooks.json | Same |
| Agent Teams | None | TeammateIdle / TaskCompleted | Same |
| MCP integration | None | None | Elicitation / ElicitationResult |
| Worktree | None | None | WorktreeCreate / WorktreeRemove |
| Config monitoring | None | None | ConfigChange / InstructionsLoaded |
| Bulk disable | None | None | disableAllHooks: true |
Related Articles for Further Learning¶
Detailed Guides by Use Case
🎯 AI Agent Automation Guide¶
- Building automated article publishing systems
- GSC integration for optimization
- Practical automation patterns
🛠️ Production Implementation Guide¶
- Error handling and retries
- Team development usage
- Best practices for production operations
🔧 Advanced Conditional Execution Guide¶
- Conditional branching with environment variables
- Execution control for specific file patterns
- Precise control for git push only during article creation
📚 Learn More¶
- Official Documentation: Hooks Guide — Introduction & Setup
- Official Documentation: Hooks Reference — Full event schema, JSON spec & detailed reference
- 🤖 Claude Code Complete Guide Hub — Systematically learn all Claude Code features
Summary¶
As of March 2026, Claude Code Hooks has grown into a full-fledged automation platform with 21 lifecycle events, 4 handler types, async execution, and JSON structured output.
- ✅ Deterministic control: Not a "request" via prompts, but "guaranteed execution"
- ✅ 21 events: Coverage from session start to end, including Agent Teams, MCP integration, and worktrees
- ✅ 4 handler types: command, http, prompt, and agent for different use cases
- ✅ Async hooks: Non-blocking execution for logs, notifications, and backups
- ✅ JSON control: Auto permission decisions, tool input rewriting, stop blocking
- ✅ Distributable: Share across teams/projects via plugins and frontmatter