Skip to content

Claude Code Complete Guide

Claude Code Hooks in Practice: AI Agent Automation Edition [2025 Latest]

Key Takeaways

  • Claude Code Hooks has expanded from the initial 4 types to 11 event types, evolving into a system that covers the entire session lifecycle
  • Hook execution types now include command (shell) / prompt (LLM evaluation) / agent (sub-agent), handling everything from simple script execution to context-aware decision making
  • PreToolUse input modification, Plugin hooks, MCP integration, and hook definitions in Skills/Subagents frontmatter significantly strengthen ecosystem-wide integration
  • CVE-2025-59536 reported in July 2025 has been patched, but configuration file handling still requires careful attention

Introduction

For those who have learned the basic concepts in the Claude Code Hooks Complete Guide, this article covers practical Hooks implementation patterns based on the latest specification and cutting-edge AI agent automation techniques.

This is a revised edition reflecting new features and specification changes added since the initial publication in January 2025. The Hooks specification has been significantly expanded with Claude Code version updates.

What Are Claude Code Hooks?

Claude Code Hooks are mechanisms that automatically execute actions at specific lifecycle points during AI agent execution. Unlike prompt-based "requests," they function as a deterministic control layer that executes reliably when conditions are met.

While instructions in CLAUDE.md or prompts are "suggestions the model is likely to follow," Hooks are "guardrails that always execute" — this is the fundamental difference.

11 Hook Event Types

Significantly expanded from the initial 4, the following 11 event types are now available.

EventTrigger TimingPrimary Use Cases
PreToolUseBefore tool executionCommand validation, dangerous operation blocking, input auto-correction
PermissionRequestWhen permission dialog appears (v2.0.45+)Automatic permission approval/denial
PostToolUseAfter tool executionCode formatting, quality checks, logging
NotificationWhen notification events occurDesktop notifications, Slack integration
UserPromptSubmitWhen user submits a promptPrompt validation, context injection
StopWhen main agent response completesTask completion verification, auto-commit
SubagentStopWhen sub-agent completesSub-agent completion verification
SubagentStartWhen sub-agent launchesSub-agent monitoring, context injection
PreCompactBefore compact operationTranscript backup
SessionStartAt session startEnvironment variable setup, dev context loading
SessionEndAt session endCleanup, log aggregation

→ See Command Reference for the full list of 15 event types

3 Hook Execution Types

In addition to traditional shell command execution, LLM-based evaluation and sub-agent verification have been added.

1. Command Hook (Shell Command)

Traditional shell command execution. Receives event JSON input via stdin and returns results through exit code and stdout.

{
  "type": "command",
  "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
}

2. Prompt Hook (LLM Evaluation)

Sends a prompt to a lightweight Claude model (Haiku) and returns approve/block decisions in JSON format. Particularly effective for "did the task really complete?" verification in Stop/SubagentStop.

{
  "type": "prompt",
  "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete and no errors remain.",
  "timeout": 30
}

3. Agent Hook (Sub-agent)

Launches a sub-agent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Useful when more complex verification logic is needed.

Implementation Guide: Basic Configuration

Configuration Files

Claude Code Hooks are configured in the following settings files.

  • ~/.claude/settings.json — User settings
  • .claude/settings.json — Project settings (tracked by Git)
  • .claude/settings.local.json — Local project settings (not tracked by Git)
  • Enterprise managed policy settings — Administrator settings

Configuration Structure

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

The matcher is a regex pattern for tool names (case-sensitive). You can specify multiple with pipes like Edit|Write, use wildcards like Notebook.*, or use * or empty string to match all tools.

The matcher is used in PreToolUse, PermissionRequest, and PostToolUse. For UserPromptSubmit, Stop, SubagentStop, etc., the matcher field is omitted.

/hooks Interactive Manager

Type /hooks within Claude Code to launch the interactive hook manager. You can view, add, and delete hooks without directly editing configuration files.

Temporarily Disabling Hooks

Add "disableAllHooks": true to your settings file, or use the toggle in the /hooks menu to temporarily disable all hooks. Note that hooks set by administrator policies cannot be disabled with this setting.

Practical Examples

Example 1: Automatic Code Quality Checks (PostToolUse)

Automatically run lint/format after file writes.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint-check.sh"
          }
        ]
      }
    ]
  }
}

Example .claude/hooks/lint-check.sh implementation:

#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

if [[ "$file_path" == *.py ]]; then
  ruff check "$file_path" && ruff format "$file_path"
elif [[ "$file_path" == *.js ]] || [[ "$file_path" == *.ts ]]; then
  npx eslint "$file_path" --fix && npx prettier --write "$file_path"
fi

Performance Note

Running formatters in PostToolUse consumes context window with every file change. For high-frequency edits, running formatting collectively in a Stop hook is more efficient.

Example 2: Blocking Dangerous Commands (PreToolUse)

PreToolUse hooks can block tool execution by returning exit code 2. Additionally, JSON output with permissionDecision enables precise control.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.sh"
          }
        ]
      }
    ]
  }
}

Example .claude/hooks/block-dangerous.sh implementation:

#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')

# Check for dangerous command patterns
if echo "$command" | grep -qE '(rm -rf /|sudo rm|chmod 777|DROP TABLE)'; then
  # Precise control via JSON output
  cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Dangerous command pattern detected: $command"
  }
}
EOF
  exit 0
fi

exit 0

Example 3: Auto-Correcting Tool Input (PreToolUse updatedInput)

Since v2.0.10, PreToolUse hooks can modify input parameters before tool execution. This enables transparent sandboxing and automatic security enforcement for Claude.

#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // empty')

# Auto-convert git push to dry-run
if [[ "$tool_name" == "Bash" ]] && echo "$command" | grep -q "git push"; then
  cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "Auto-converted to dry-run",
    "updatedInput": {
      "command": "${command} --dry-run"
    }
  }
}
EOF
  exit 0
fi

exit 0

Example 4: Context Injection at Session Start (SessionStart)

SessionStart hooks can automatically load development context and persist environment variables.

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-init.sh"
          }
        ]
      }
    ]
  }
}

Example .claude/hooks/session-init.sh implementation:

#!/bin/bash

# Persist environment variables (CLAUDE_ENV_FILE is SessionStart-only)
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi

# Inject context information via JSON output
recent_changes=$(git log --oneline -5 2>/dev/null || echo "No git history")
open_issues=$(git log --oneline --all --grep="TODO" -3 2>/dev/null || echo "No issues found")

cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Recent changes:\n${recent_changes}\n\nOpen items:\n${open_issues}"
  }
}
EOF
exit 0

Example 5: Intelligent Stop Hook (Prompt Type)

Use an LLM-based Prompt hook to evaluate task completion status with context awareness.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Tests have been run and pass\n\nRespond with JSON: {\"decision\": \"approve\" or \"block\", \"reason\": \"your explanation\"}",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Advanced Usage Patterns

1. MCP Tools Integration

Hooks can be configured for tools provided via MCP (Model Context Protocol). MCP tools follow the mcp__<server>__<tool> naming pattern.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo '[MCP] Memory operation' >> ~/mcp-audit.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

2. Plugin Hooks

Plugins can provide hooks that integrate seamlessly with user and project hooks. Plugin hooks are auto-merged when the plugin is enabled and execute in parallel with existing hooks.

{
  "description": "Automatic code formatting plugin",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

In plugins, the ${CLAUDE_PLUGIN_ROOT} environment variable provides the absolute path to the plugin directory.

3. Hook Definitions in Skills/Subagents Frontmatter

In addition to configuration files, hooks can be defined directly in Skills and Subagents YAML frontmatter. These hooks are scoped to the component lifecycle and only execute while that component is active.

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

Sub-agent Stop hooks are automatically converted to SubagentStop.

4. Audit Logging and PermissionRequest Auto-Control

The PermissionRequest hook (v2.0.45+) automates responses to permission dialogs.

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-approve-reads.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/auto-approve-reads.sh
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

# Auto-approve reading document files
if [[ "$file_path" == *.md ]] || [[ "$file_path" == *.txt ]] || [[ "$file_path" == *.json ]]; then
  cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow"
    }
  }
}
EOF
  exit 0
fi

# Otherwise, fall through to default permission flow
exit 0

→ For permission configuration details, see the Auto-Approval Settings Guide

5. Multi-Agent Orchestration

Combining SubagentStop hooks with Stop hooks enables automated task handoff between sub-agents.

{
  "hooks": {
    "SubagentStop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/subagent-handoff.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-pipeline.sh"
          }
        ]
      }
    ]
  }
}

Checking the stop_hook_active flag to prevent infinite loops is important.

→ For Agent Teams details, see the Agent Teams Guide

Hook Input/Output Details

Input (stdin JSON)

All hooks receive JSON via stdin containing the following common fields.

{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/path/to/project",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  },
  "tool_use_id": "toolu_01ABC123..."
}

Output Control

Two methods are available (mutually exclusive).

Method 1: Exit Code

  • exit 0 — Success (stdout displayed in verbose mode. For UserPromptSubmit/SessionStart, added as context)
  • exit 2 — Block (stderr notified to Claude as error message. JSON output is ignored)
  • Other — Non-blocking error (stderr displayed in verbose mode)

Method 2: JSON Output (combined with exit 0)

{
  "continue": true,
  "stopReason": "Custom stop message",
  "suppressOutput": false,
  "systemMessage": "Warning shown to user",
  "decision": "block",
  "reason": "Explanation",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "Auto-approved",
    "updatedInput": {},
    "additionalContext": "Extra context for Claude"
  }
}

Security Considerations

CVE-2025-59536: RCE Vulnerability via Configuration Files

In July 2025, Check Point Research reported an arbitrary code execution vulnerability through .claude/settings.json. There was a risk of hooks auto-executing when cloning malicious repositories, and Anthropic released a fix in August 2025.

Security Best Practices

  • Always validate and sanitize input in hook scripts
  • Always quote shell variables (use "$VAR", not $VAR)
  • Check for path traversal (..) in file paths
  • Use absolute paths or $CLAUDE_PROJECT_DIR for script paths
  • Avoid accessing sensitive files such as .env, .git/, and key files
  • Hook configuration changes are captured as snapshots at session startup and mid-session changes are not immediately applied (review via /hooks menu is required)

Enterprise Management

The allowManagedHooksOnly setting restricts hooks to only those approved by the organization. Hooks configured via administrator policies cannot be disabled by the user-side disableAllHooks setting.

Troubleshooting

Debugging Steps

  1. Check registration status with /hooks
  2. View execution details with claude --debug
  3. Manually execute hook commands to test
  4. Verify script execution permissions (chmod +x)
  5. Validate JSON configuration syntax

Common Issues

Hook not firing

  • Verify the matcher tool name is exact (case-sensitive: Bash, not bash)
  • Verify event names are PascalCase (PreToolUse, not preToolUse)

JSON output not being parsed

  • Check that shell profile (.bashrc, etc.) output is not contaminating stdout
  • JSON output is only processed with exit code 0 (exit 2 uses only stderr)

Timeout issues

  • Default is 60 seconds. Each hook can set a custom timeout field
{
  "type": "command",
  "command": "your-slow-command",
  "timeout": 120
}

Environment Variable Reference

VariableDescriptionAvailable In
$CLAUDE_PROJECT_DIRAbsolute path to project rootAll hooks
$CLAUDE_ENV_FILEFile path for persisting environment variablesSessionStart only
$CLAUDE_CODE_REMOTE"true" in remote (web) environments, unset in local CLIAll hooks
${CLAUDE_PLUGIN_ROOT}Absolute path to plugin directoryPlugin hooks

Summary

Claude Code Hooks has evolved from an initial system of 4 events with command-only execution to an ecosystem-integrated automation platform with 11 events and 3 execution types. With Plugin hooks, MCP integration, and definitions in Skills/Subagents frontmatter, integration with Claude Code's features has deepened, enabling deterministic control across the entire development workflow.

When properly implemented, it enables the transition from "expectations of the model" to "guaranteed execution." However, since hooks execute arbitrary commands with user permissions, security considerations are essential.


This article was originally published on January 18, 2025, and fully revised on February 27, 2026 based on the latest specification. Please refer to the official documentation for the most current information.