Have you ever wondered: can you automatically run some checks before Claude Code performs certain actions? Or auto-commit code when a session ends?

The Hooks system does exactly this. It lets you “jump in” at key lifecycle points of the AI Agent to execute custom logic.

What Are Hooks? Custom Interception Points

Hooks are like airport security checkpoints: set up inspection points at specific locations, and qualified passengers (operations) must pass inspection to proceed.

Claude Code supports 26 Hook events, covering the entire lifecycle:

Session Lifecycle:

  • SessionStart: Session begins
  • Setup: Repository initialization
  • SessionEnd: Session ends

Tool Execution Lifecycle:

  • PreToolUse: Before tool execution
  • PostToolUse: Tool executed successfully
  • PostToolUseFailure: Tool execution failed
  • PermissionRequest: When permission requested
  • PermissionDenied: Permission denied

Response Lifecycle:

  • UserPromptSubmit: User submits prompt
  • Stop: AI about to end response
  • StopFailure: Round ended due to API error

Multi-Agent Lifecycle:

  • SubagentStart: Subagent starts
  • SubagentStop: Subagent ends
  • TeammateIdle: Teammate becomes idle
  • TaskCreated/Completed: Task created/completed

File and Config Changes:

  • FileChanged: File change detected
  • CwdChanged: Working directory changed
  • ConfigChange: Config changed
  • InstructionsLoaded: CLAUDE.md loaded

Other:

  • PreCompact/PostCompact: Before/after compaction
  • Elicitation/ElicitationResult: MCP interaction
  • WorktreeCreate/Remove: Worktree operations

Four Hook Types

Hooks support four execution methods:

1. command Type: Shell Commands

The most basic approach, executes shell scripts:

{
  "type": "command",
  "command": "echo 'Hello from hook'",
  "shell": "bash",
  "timeout": 60,
  "if": "Bash(git *)"
}

Characteristics:

  • Supports bash and powershell
  • Can use if condition filtering (permission rule syntax)
  • Default timeout is 10 minutes, SessionEnd defaults to 1.5 seconds

2. prompt Type: LLM Evaluation

Sends Hook input to a lightweight LLM for evaluation:

{
  "type": "prompt",
  "prompt": "Check if this operation is safe: $ARGUMENTS",
  "model": "claude-haiku-4-5"
}

$ARGUMENTS gets replaced with Hook input JSON.

3. agent Type: Agent Validator

Starts a complete Agent loop to verify conditions:

{
  "type": "agent",
  "prompt": "Check if unit tests pass",
  "timeout": 120,
  "model": "claude-sonnet-4-6"
}

This is the most powerful type, can execute complex validations.

4. http Type: Webhook

POSTs to specified URL:

{
  "type": "http",
  "url": "https://example.com/hook",
  "headers": {"Authorization": "Bearer $TOKEN"},
  "allowedEnvVars": ["TOKEN"]
}

Note: HTTP Hook doesn’t support SessionStart and Setup events.

Exit Code Semantics: How Hooks Communicate with Claude Code

Hooks pass decisions through exit codes:

Exit CodeMeaningBehavior
0Success/Allowstdout/stderr not shown (or only in transcript mode)
2Blocking errorstderr sent to model, blocks current operation
OtherNon-blocking errorstderr only shown to user, operation continues

Different events have special behaviors with exit code 2:

  • PreToolUse: Blocks tool call, stderr to model
  • Stop: stderr to model, but continues conversation (implements “keep coding” mode)
  • UserPromptSubmit: Blocks processing, erases original prompt, only shows stderr
  • SessionStart/Setup: Blocking errors ignored (can’t block startup)

JSON Output Protocol

Beyond exit codes, Hooks can output JSON via stdout to pass structured information:

{
  "continue": false,
  "decision": "block",
  "reason": "Code format doesn't match prettier",
  "systemMessage": "Please format code before submitting"
}

For PreToolUse events, can also modify tool input:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "updatedInput": {
      "file_path": "/new/path.ts"
    }
  }
}

Practical Configuration Examples

Example 1: PreToolUse Format Check

Automatically check format before writing TypeScript files:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(echo $ARGUMENTS | jq -r '.file_path') && prettier --check \"$CLAUDE_PROJECT_DIR/$FILE\" || echo '{\"decision\":\"block\",\"reason\":\"Format check failed\"}'",
            "if": "Write(*.ts)",
            "statusMessage": "Checking format..."
          }
        ]
      }
    ]
  }
}

Example 2: Auto Commit

Auto-commit unsaved changes when session ends:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Check for uncommitted changes, if any, create appropriate commit and push",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Example 3: Environment Initialization

Set up development environment when session starts:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'export NODE_ENV=development' >> $CLAUDE_ENV_FILE",
            "statusMessage": "Setting up development environment..."
          }
        ]
      }
    ]
  }
}

Async and Background Execution

Hooks can execute in background in two ways:

Configuration Declaration

{
  "type": "command",
  "command": "long-running-task",
  "async": true
}

Or:

{
  "asyncRewake": true
}

asyncRewake special: when a background Hook exits with code 2, it wakes the model to continue processing.

Runtime Declaration

Hook outputs {"async": true} on first line, immediately enters background.

Trust Gating

Hook execution requires trust confirmation:

  • Non-interactive mode (SDK): Trust implied, executes directly
  • Interactive mode: Requires trust dialog confirmation

This is defense in depth: even if someone maliciously configures dangerous hooks, users won’t execute them if they don’t click “Trust.”

Configuration Sources and Merging

Hooks can come from multiple sources:

  1. Config snapshot (settings.json)
  2. Registered Hooks (SDK callbacks, plugin native hooks)
  3. Session Hooks (Agent frontmatter registration)
  4. Session function Hooks (structured output enforcer)

Sources merge by priority, support allowManagedHooksOnly policy to restrict.

Pattern Extraction

Pattern 1: Exit Code as Protocol

Shell commands and host processes need lightweight semantic communication. Define clear exit code contracts:

  • 0 = Success/Allow
  • 2 = Blocking error
  • Other = Non-blocking error

Pattern 2: Config Snapshot Isolation

Config files may be modified at runtime. Capture snapshot at startup, use snapshot at runtime instead of reading live. Update snapshot when user explicitly modifies.

Pattern 3: Namespace Deduplication

Same Hook may appear in multiple config sources. Deduplication key includes source context, same command in different plugins stays independent, merges at same source level.

Summary

The Hooks system is the extensibility core of Claude Code:

  • 26 event points: Cover complete lifecycle
  • Four types: command, prompt, agent, http
  • Exit code semantics: Simple effective communication protocol
  • Trust gating: Secure defaults
  • Config merging: Multi-source support

It’s like:

  • Airport security (interception checkpoints)
  • Autonomous driving takeover mechanism (human intervention)
  • CI/CD pipelines (automation flow)

Understanding Hooks lets you:

  • Customize Claude Code behavior
  • Implement project-specific automation
  • Design extension points in your own AI Agent

Next up: Agent Clusters - When AI Starts “Group Battling.”