Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/anthropics/claude-code/llms.txt

Use this file to discover all available pages before exploring further.

Claude Code provides lifecycle hooks for various events during session execution, tool usage, and system operations.

Session Lifecycle

SessionStart

Runs when a new session starts or resumes. Timing: After session initialization, before first user message Configuration:
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ${CLAUDE_PLUGIN_ROOT}/setup-session.sh"
          }
        ]
      }
    ]
  }
}
Input:
{
  "session_id": "abc123",
  "working_directory": "/path/to/project",
  "is_resume": false
}
Use cases:
  • Environment configuration
  • Loading project-specific context
  • Initializing external services
  • Setting up Docker containers
  • Loading API credentials
SessionStart hooks are deferred during startup to reduce time-to-interactive by ~500ms.
Example:
setup-session.sh
#!/bin/bash
# Configure environment for Claude session

export PROJECT_ENV="development"
source .env.local 2>/dev/null || true

# Start development services
docker-compose up -d db redis

echo "Session environment configured" >&2
exit 0

Stop

Runs when a session is ending (user exit or interruption). Timing: Before session cleanup and exit Configuration:
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh"
          }
        ]
      }
    ]
  }
}
Input:
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "last_assistant_message": "Final message from Claude",
  "working_directory": "/path/to/project"
}
Output (optional):
{
  "decision": "block",
  "reason": "Continue with new prompt",
  "systemMessage": "Loop iteration 5"
}
Stop hooks can block session exit by returning "decision": "block". This enables continuous loop patterns.
Use cases:
  • Cleanup operations
  • Saving session state
  • Stopping services
  • Creating session summaries
  • Continuous loop workflows (Ralph Wiggum pattern)
Example (Ralph Wiggum Loop):
stop-hook.sh
#!/bin/bash
# Prevent exit and continue loop

set -euo pipefail

# Read hook input
HOOK_INPUT=$(cat)

# Check if loop is active
if [[ ! -f ".claude/ralph-loop.local.md" ]]; then
  exit 0  # Allow exit
fi

# Parse state
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' ".claude/ralph-loop.local.md")
ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//')

# Check if max reached
if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then
  rm ".claude/ralph-loop.local.md"
  exit 0  # Allow exit
fi

# Continue loop
NEXT_ITERATION=$((ITERATION + 1))
PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' ".claude/ralph-loop.local.md")

# Update state
sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" ".claude/ralph-loop.local.md" > tmp
mv tmp ".claude/ralph-loop.local.md"

# Block exit and feed prompt back
jq -n \
  --arg prompt "$PROMPT_TEXT" \
  --arg msg "🔄 Ralph iteration $NEXT_ITERATION" \
  '{
    "decision": "block",
    "reason": $prompt,
    "systemMessage": $msg
  }'

exit 0

Setup

Runs for repository setup and maintenance operations. Timing: When invoked via CLI flags: --init, --init-only, or --maintenance Configuration:
{
  "hooks": {
    "Setup": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ./scripts/setup.sh"
          }
        ]
      }
    ]
  }
}
Input:
{
  "mode": "init",
  "working_directory": "/path/to/project"
}
Use cases:
  • Repository initialization
  • Dependency installation
  • Database migrations
  • Environment setup
  • Periodic maintenance tasks
Example:
# Run setup on new clone
claude --init

# Run setup without starting session
claude --init-only

# Run maintenance tasks
claude --maintenance

Tool Lifecycle

PreToolUse

Runs before a tool is executed. Timing: After Claude requests tool use, before execution Configuration:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/validator.py"
          }
        ]
      }
    ]
  }
}
Input:
{
  "session_id": "abc123",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git push origin main",
    "workdir": "/path/to/project"
  }
}
Output:
{
  "decision": "block",
  "additionalContext": "This command requires manual approval"
}
Exit codes:
  • 0 - Allow execution
  • 1 - Show stderr to user only (allow execution)
  • 2 - Block execution, show stderr to Claude
Use cases:
  • Command validation
  • Security checks
  • Command transformation
  • Rate limiting
  • Audit logging
Example (Command Validator):
validator.py
#!/usr/bin/env python3
import json
import sys
import re

VALIDATION_RULES = [
    (
        r"^grep\b(?!.*\|)",
        "Use 'rg' (ripgrep) instead of 'grep' for better performance",
    ),
    (
        r"^find\s+\S+\s+-name\b",
        "Use 'rg --files -g pattern' instead of 'find -name'",
    ),
]

def validate_command(command: str) -> list[str]:
    issues = []
    for pattern, message in VALIDATION_RULES:
        if re.search(pattern, command):
            issues.append(message)
    return issues

try:
    data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = data.get("tool_name", "")
if tool_name != "Bash":
    sys.exit(0)

command = data.get("tool_input", {}).get("command", "")
if not command:
    sys.exit(0)

issues = validate_command(command)
if issues:
    for msg in issues:
        print(f"• {msg}", file=sys.stderr)
    sys.exit(2)  # Block execution

sys.exit(0)  # Allow execution

PostToolUse

Runs after a tool completes execution. Timing: After tool execution, before result is sent to Claude Configuration:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/logger.py"
          }
        ]
      }
    ]
  }
}
Input:
{
  "session_id": "abc123",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  },
  "tool_output": {
    "stdout": "All tests passed",
    "stderr": "",
    "exit_code": 0
  }
}
Use cases:
  • Result logging
  • Metrics collection
  • Notification triggers
  • Result transformation
  • Error analysis

Git Worktree Lifecycle

WorktreeCreate

Runs when a git worktree is created for agent isolation. Timing: After worktree creation, before agent starts Configuration:
{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ./scripts/setup-worktree.sh"
          }
        ]
      }
    ]
  }
}
Input:
{
  "worktree_path": "/path/to/worktree",
  "branch_name": "claude/task-123",
  "base_branch": "main"
}
Use cases:
  • Worktree-specific configuration
  • Installing dependencies
  • Database setup
  • Environment configuration

WorktreeRemove

Runs when a git worktree is being removed. Timing: Before worktree deletion Configuration:
{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ./scripts/cleanup-worktree.sh"
          }
        ]
      }
    ]
  }
}
Input:
{
  "worktree_path": "/path/to/worktree",
  "branch_name": "claude/task-123"
}
Use cases:
  • Cleanup operations
  • Stopping services
  • Collecting artifacts
  • Backup operations

Configuration Lifecycle

ConfigChange

Runs when configuration files change during a session. Timing: When settings.json or related config files are modified Configuration:
{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/audit-config.py"
          }
        ]
      }
    ]
  }
}
Input:
{
  "session_id": "abc123",
  "config_file": "/path/to/settings.json",
  "changes": {
    "permissions.allow": ["Bash"]
  }
}
Output (optional):
{
  "decision": "block",
  "reason": "Configuration changes require administrator approval"
}
Use cases:
  • Enterprise security auditing
  • Configuration validation
  • Change approval workflows
  • Compliance enforcement

Agent Team Lifecycle

TeammateIdle

Runs when an agent teammate becomes idle. Timing: When teammate completes work and awaits instructions Configuration:
{
  "hooks": {
    "TeammateIdle": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/notify.py"
          }
        ]
      }
    ]
  }
}
Input:
{
  "session_id": "abc123",
  "agent_id": "teammate-1",
  "agent_type": "code-reviewer"
}

TaskCompleted

Runs when a background task completes. Timing: When task finishes successfully or with error Configuration:
{
  "hooks": {
    "TaskCompleted": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/task-logger.py"
          }
        ]
      }
    ]
  }
}
Input:
{
  "session_id": "abc123",
  "task_id": "task-1",
  "status": "success",
  "result": "Task completed successfully",
  "duration_ms": 5432
}

SubagentStop

Runs when a subagent session ends. Timing: After subagent completes, before returning to parent Input:
{
  "session_id": "abc123",
  "parent_session_id": "parent-123",
  "agent_type": "code-analyzer",
  "last_assistant_message": "Analysis complete"
}

Best Practices

Hooks run synchronously and block execution:
  • Target < 100ms execution time
  • Use background processes for slow work
  • Cache expensive operations
  • Avoid network calls when possible
try:
    data = json.load(sys.stdin)
    # Process data
except Exception as e:
    print(f"Hook error: {e}", file=sys.stderr)
    sys.exit(1)  # Show error to user, allow execution
  • 0 - Success, allow operation
  • 1 - Non-blocking error (user sees stderr)
  • 2 - Blocking error (Claude sees stderr)
  • Choose based on whether Claude should know about the issue
if "tool_input" not in data:
    sys.exit(0)  # Gracefully handle missing data

if data.get("tool_name") != "Bash":
    sys.exit(0)  # Only process relevant tools
# Store session-specific state
STATE_FILE = f"~/.claude/hook-state-{session_id}.json"

# Clean up old state files
cleanup_old_states()  # Runs periodically

Next Steps

Hook Examples

See real-world hook implementations and patterns

Build docs developers (and LLMs) love