Skip to content

Claude Code Complete Guide

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.json snippets 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/*.md is 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 --worktree or isolation: "worktree" Example: Custom worktree creation logic

  • WorktreeRemove

    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.

TypeDescriptionUse CaseSupported Events
commandExecute shell commandslint, git operations, notifications, backupsAll events
httpPOST event data to an HTTP endpointExternal service integration, shared audit loggingAll events
promptSingle-turn LLM evaluationContext-dependent allow/deny decisionsPreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, Stop, SubagentStop, TaskCompleted, etc.
agentMulti-turn agent with tool accessComplex decisions based on actual file stateSame 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 behaviorWaits for hook completionContinues immediately without waiting
Block controlCan block with exit code 2Cannot block
Best forSecurity checks, permission decisionsLogging, backups, sending notifications
Not suitable forTime-consuming operationsOperations 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:

EventEffect of Exit 2
PreToolUseBlocks the tool call
UserPromptSubmitRejects the prompt
PermissionRequestDenies permission
Stop / SubagentStopBlocks stopping (forces continued work)
TeammateIdlePrevents teammate from going idle
TaskCompletedPrevents task completion marking
ConfigChangeBlocks the configuration change
ElicitationDeclines the MCP input request
ElicitationResultBlocks the response (changes to decline)
SessionStart / PreCompactOnly displays stderr (cannot block)
PostToolUse / NotificationOnly 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

LocationScopeShareable
~/.claude/settings.jsonAll projectsNo (local only)
.claude/settings.jsonSingle projectYes (can be committed)
.claude/settings.local.jsonSingle projectNo (gitignored)
Managed policy settingsOrganization-wideYes (admin-controlled)
Plugin hooks/hooks.jsonWhen plugin enabledYes (bundled)
Skill/agent frontmatterWhile component activeYes (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|MultiEdit in matcher

  • Security Blocking

    Block dangerous Bash commands (rm -rf, etc.) with exit code 2 in PreToolUse

  • Desktop Notifications

    Send OS notifications on idle or permission requests via Notification

  • Safe Command Auto-Approval

    Auto-allow npm run lint etc. via PermissionRequest

  • Test 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

SymptomCauseSolution
Hook doesn't executeNot shown in /hooksRestart session, check config file syntax
"command not found"Relative path issueUse absolute paths with $CLAUDE_PROJECT_DIR
"jq: command not found"jq not installedInstall jq, or use Python/Node.js alternative
PermissionRequest doesn't fireNon-interactive modeUse PreToolUse when using -p flag
Can't block with async hookasync: true limitationUse sync hooks for security checks
JSON validation failedShell profile echo pollutes JSON outputAdd if [[ $- == *i* ]] guard in ~/.zshrc
Stop hook runs foreverstop_hook_active not checkedImplement the infinite loop prevention pattern above
Settings changes not reflectedFile edited mid-sessionCheck /hooks or restart session

Using Verbose Mode

# Toggle verbose mode with Ctrl+O
# → stdout/stderr from PreToolUse/PostToolUse etc. will be displayed

Version Changelog

ItemPrevious (2025)February 2026Current (March 2026)
Number of events71421
Hook handler typescommand onlycommand / prompt / agentcommand / http / prompt / agent
Async executionNoneasync: true supportSame
JSON output controlNonepermissionDecision, etc.Same
Configuration methodManual JSON only/hooks CLI menu + JSON/hooks (read-only) + JSON + frontmatter
Environment variable persistenceNoneCLAUDE_ENV_FILESame
Plugin supportNonehooks/hooks.jsonSame
Agent TeamsNoneTeammateIdle / TaskCompletedSame
MCP integrationNoneNoneElicitation / ElicitationResult
WorktreeNoneNoneWorktreeCreate / WorktreeRemove
Config monitoringNoneNoneConfigChange / InstructionsLoaded
Bulk disableNoneNonedisableAllHooks: true

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

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