Skip to main content

What Are Hooks?

Hooks are scripts that execute at specific points in Claude Code’s lifecycle:
  • Before and after tool use (PreToolUse, PostToolUse)
  • On user interactions (UserPromptSubmit, PermissionRequest)
  • Session lifecycle (SessionStart, SessionEnd)
  • Agent lifecycle (SubagentStart, SubagentStop)
  • System events (PreCompact, ConfigChange)
Hooks run outside the agentic loop — they’re deterministic scripts, not AI reasoning.
Hooks are configured in .claude/settings.json under the hooks key, or in agent/skill frontmatter.

Why Hooks Matter

Hooks enable deterministic automation:
  • Validation: Block dangerous commands before execution
  • Notifications: Sound alerts when agents complete tasks
  • Logging: Audit all tool usage and decisions
  • Integration: Trigger external systems (CI/CD, monitoring, Slack)
  • Context injection: Add dynamic data to prompts
  • Decision control: Approve/deny actions programmatically
Hooks are powerful but should be used carefully. They can block Claude Code’s execution or modify behavior in ways that affect all sessions.

Hook Events (18 Total)

Claude Code provides 18 hook events:
HookWhen It FiresCan Block
PreToolUseBefore every tool callYes
PostToolUseAfter tool succeedsNo
PostToolUseFailureAfter tool failsNo
PermissionRequestWhen permission prompt appearsYes

Hook Types

Claude Code supports three hook types:

Command Hooks

Most commonRuns a shell command. Receives JSON input via stdin, returns via stdout/exit code.
{
  "type": "command",
  "command": "python3 hook.py"
}

Prompt Hooks

AI-poweredSends a prompt to Claude for single-turn evaluation. Returns {"ok": true/false}.
{
  "type": "prompt",
  "prompt": "Check if tests pass"
}

Agent Hooks

Multi-turnSpawns a subagent with tool access for complex verification.
{
  "type": "agent",
  "prompt": "Verify deployment"
}

Configuration

Settings-Based Hooks

Defined in .claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/validate.py",
            "timeout": 5000,
            "async": true
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Session started!'",
            "timeout": 1000,
            "once": true
          }
        ]
      }
    ]
  },
  "disableAllHooks": false
}

Agent/Skill Frontmatter Hooks

Scoped to specific agents or skills:
---
name: weather-agent
hooks:
  PreToolUse:
    - matcher: ".*"
      hooks:
        - type: command
          command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=weather-agent
          timeout: 5000
          async: true
  PostToolUse:
    - matcher: ".*"
      hooks:
        - type: command
          command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=weather-agent
          timeout: 5000
          async: true
  Stop:
    - hooks:
        - type: command
          command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=weather-agent
          timeout: 5000
          async: true
---
Agent hooks support 6 events: PreToolUse, PostToolUse, PermissionRequest, PostToolUseFailure, Stop (converted to SubagentStop at runtime), SubagentStop.

Hook Properties

type
string
required
Hook type: command, prompt, or agent
command
string
Shell command to execute (for type: "command").Supports environment variables like ${CLAUDE_PROJECT_DIR}.
prompt
string
LLM prompt for evaluation (for type: "prompt" or type: "agent").
timeout
number
default:"60000"
Timeout in milliseconds. Hook is killed if it exceeds this duration.
async
boolean
default:"false"
Run hook in background without blocking Claude Code.Use for: Logging, notifications, side effects.
once
boolean
default:"false"
Run only once per session.Use for: SessionStart, SessionEnd, Setup.
matcher
string
Regex pattern to filter events.Examples:
  • "Bash" — Only Bash tool
  • "Edit|Write" — Edit or Write tools
  • "mcp__.*" — All MCP tools
  • ".*" — All events (default)
model
string
Model for prompt-based hooks.Accepted values: haiku, sonnet, opus

Real Example: Voice Hook System

From the reference repository at .claude/hooks/: Structure:
.claude/hooks/
├── scripts/
│   └── hooks.py              # Main handler
├── config/
│   ├── hooks-config.json     # Shared config
│   └── hooks-config.local.json  # Personal overrides
└── sounds/
    ├── pretooluse/
    │   ├── pretooluse.wav
    │   └── pretooluse-git-committing.wav
    ├── posttooluse/
    │   └── posttooluse.wav
    ├── stop/
    │   └── stop.wav
    └── agent_pretooluse/    # Agent-specific sounds
        └── agent_pretooluse.wav
Configuration in .claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py",
            "timeout": 5000,
            "async": true,
            "statusMessage": "PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py",
            "timeout": 5000,
            "async": true,
            "statusMessage": "PostToolUse"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py",
            "timeout": 5000,
            "async": true,
            "statusMessage": "Stop"
          }
        ]
      }
    ]
  }
}
Key Features:
  • Cross-platform sound playback (macOS: afplay, Linux: paplay, Windows: winsound)
  • Configurable per-hook enable/disable via hooks-config.json
  • Special handling: git commits trigger pretooluse-git-committing sound
  • Agent-specific sounds in separate folders

Voice Hook Repository

Complete voice notification system with sound files and cross-platform support

Environment Variables

Available to hook scripts:
VariableAvailabilityDescription
$CLAUDE_PROJECT_DIRAll hooksProject root directory
$CLAUDE_ENV_FILESessionStartFile path for persisting environment variables
${CLAUDE_PLUGIN_ROOT}Plugin hooksPlugin’s root directory
$CLAUDE_CODE_REMOTEAll hooksSet to "true" in remote web environments
JSON Input (via stdin): All hooks receive JSON on stdin:
{
  "hook_event_name": "PreToolUse",
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.json",
  "cwd": "/project/path",
  "permission_mode": "default",
  "tool_name": "Bash",
  "tool_input": {"command": "npm test"}
}

Hook Matchers

Some hooks support matchers to filter events:
HookMatcher FieldExample
PreToolUsetool_name"Bash", "mcp__.*"
PostToolUsetool_name"Write"
PermissionRequesttool_name"Edit"
Notificationnotification_type"permission_prompt"
SubagentStartagent_type"Bash", "my-agent"
SubagentStopagent_type"Bash", "my-agent"
SessionStartsource"startup", "resume"
ConfigChangesource"project_settings"
No matcher support: UserPromptSubmit, Stop, TeammateIdle, TaskCompleted, WorktreeCreate, WorktreeRemove, Setup

Decision Control

Hooks can block operations or modify behavior:

PreToolUse (Block Tool Calls)

{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Dangerous command detected"
  }
}
Values: allow, deny, ask

Stop / SubagentStop (Block Continuation)

{
  "decision": "block",
  "stopReason": "Tests failed"
}

UserPromptSubmit (Modify Prompt)

import json
import sys

data = json.load(sys.stdin)
modified_prompt = data['prompt'] + "\n\nAdditional context..."

print(json.dumps({"prompt": modified_prompt}))

Best Practices

Logging, notifications, and analytics should run async:
{
  "async": true,
  "timeout": 5000
}
This prevents hooks from slowing down Claude Code.
In .claude/settings.local.json:
{
  "disableAllHooks": true
}
Or disable individual hooks in hooks-config.json.
Only fire hooks for relevant events:
{
  "matcher": "Bash(rm *|shred *)",
  "hooks": [/* validation */]
}
Exit codes:
  • 0 — Success, continue
  • 1 — Error (logged, continues)
  • 2 — Block the operation
Always validate input JSON and handle missing fields.
SessionStart, SessionEnd, and Setup should only run once:
{
  "once": true
}

Disabling Hooks

Disable All Hooks

In .claude/settings.local.json:
{
  "disableAllHooks": true
}

Disable Individual Hooks

In .claude/hooks/config/hooks-config.json:
{
  "disablePreToolUseHook": true,
  "disablePostToolUseHook": false,
  "disableStopHook": true
}
Local Overrides: Create .claude/hooks/config/hooks-config.local.json (git-ignored) for personal preferences.

Hooks Management

claude
/hooks
View, add, and delete hooks without editing JSON. Hooks are labeled by source: [User], [Project], [Local], [Plugin].

Settings

Configure hooks in settings.json

Subagents

Add lifecycle hooks to agents

Voice Hooks Example

Complete voice notification system

Settings Best Practice

Full hook configuration reference

Further Reading

Build docs developers (and LLMs) love