What Are Claude Code Hooks | Complete Event List, Configuration & Automation Guide

AI Chat Article Summarypowered by Claude

Claude Code Hooks are user-defined commands that run automatically at specific points in the session lifecycle. Automatic formatting after file edits, instant blocking of dangerous commands, desktop notifications when work completes — these can all be achieved deterministically, without relying on the model's "judgment." This article, based on Anthropic's official documentation (Hooks Reference and Automating with Hooks Guide), explains everything from the complete event list to configuration methods and practical use cases.

結論powered by Claude
Claude Code Hooks are user-defined commands that run automatically at specific points in the session lifecycle. Their key feature is that they operate deterministically — without relying on the model's judgment — enabling automatic formatting after file edits, instant blocking of dangerous commands, and notifications upon task completion.

Hooks provide nearly 30 types of events including PreToolUse, PostToolUse, SessionStart, and Stop, with handlers available in 5 types: command, http, prompt, agent, and more. Using matchers, you can trigger hooks only for specific tools or argument patterns.

Configuration is written in JSON in either ~/.claude/settings.json (shared across all projects) or .claude/settings.json (project-specific). This article, based on Anthropic's official documentation, covers everything from the complete event list to configuration methods and practical use cases.

目次 (13)

The Problem Hooks Solve

Claude Code writes code and executes commands as instructed, but enforcing rules such as "always run Prettier after every edit" or "unconditionally reject writes to .env" depends on the model's discretion. If the model decides "this isn't necessary right now," it won't be executed.

Hooks solve this problem. Every time a specific event fires, a shell command or HTTP endpoint is invoked and the action executes deterministically, without going through the LLM's judgment. Anthropic officially states that "Hooks guarantee that certain actions always occur."

Complete Event List

Hooks are categorized by when they fire. The most common ones are shown below.

Event When It Fires Primary Use
SessionStart When a session starts or resumes Environment setup, context injection
UserPromptSubmit When a prompt is submitted Prompt validation, adding extra context
PreToolUse Before a tool executes Blocking or approving tool calls
PostToolUse After a tool executes Lint/formatting, logging
PermissionRequest When a permission dialog appears Auto-approve or auto-deny
Stop When Claude completes a response Task completion check, notifications
Notification When Claude is waiting for input Desktop notifications
SessionEnd When a session ends Cleanup processing
ConfigChange When a config file is modified Audit logging
CwdChanged When the working directory changes direnv integration
FileChanged When a watched file is modified Environment reload
PreCompact / PostCompact Before and after context compaction Context re-injection

The official complete list defines nearly 30 events including SubagentStart, TaskCreated, and WorktreeCreate.

The 5 Handler Types

Hooks can be executed in 5 different ways.

  1. command (most common): Executes a shell command. JSON is passed via stdin
  2. http: POSTs to a specified URL. Can delegate to cloud functions or internal services
  3. mcp_tool: Calls a tool on a connected MCP server
  4. prompt: Has an LLM (Haiku by default) make a judgment. Useful for conditional logic that doesn't fit into rules
  5. agent: Launches a sub-execution environment with file reading and search tools. Used for complex validation

command and http are deterministic; prompt and agent are judgment-based via an LLM.

Configuration Files and Scope

Hooks are written in JSON and placed in the appropriate file based on scope.

File Path Scope Committable to Repository
~/.claude/settings.json Shared across all projects No (machine-local)
.claude/settings.json Single project Yes
.claude/settings.local.json Single project No (gitignored)

The basic structure has 3 layers.

{
  "hooks": {
    "PostToolUse": [         // ① Event name
      {
        "matcher": "Edit|Write",   // ② Matcher
        "hooks": [           // ③ Handler
          {
            "type": "command",
            "command": "..."
          }
        ]
      }
    ]
  }
}

After configuration, you can type /hooks in the CLI to view the list of registered hooks.

Narrowing Trigger Targets with Matchers

Using the matcher field, you can restrict a hook to specific tools or events rather than all invocations.

  • "" → Specifying an empty string matches all events (no filter)
  • "Bash" → Only Bash tool calls
  • "Edit|Write" → Only Edit or Write (pipe for OR logic)
  • "mcp__github__.*" → All tools on the GitHub MCP server (regex)
  • "compact" → Only during compact resume within SessionStart

You can also use the if field to filter by argument content in addition to tool name (Claude Code v2.1.85 and later).

{
  "if": "Bash(git *)",
  "command": "..."
}

This ensures the hook process only starts for git commands, preventing unnecessary process creation for unrelated Bash calls.

Practical Use Cases

Receiving Desktop Notifications When Work Completes

When Claude is waiting for input, you can receive notifications without having to watch the terminal.

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
          }
        ]
      }
    ]
  }
}

Specifying an empty string for matcher matches all events. Use "" when no filtering is needed.

On macOS, use osascript -e 'display notification "..."'; on Windows (PowerShell), use the MessageBox API.

Running Prettier Automatically After Edits

Set a PostToolUse hook with the Edit|Write matcher, extract the edited file path with jq, and pass it to Prettier.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

No matter how many files Claude edits, you'll never need to run Prettier manually again.

Blocking Writes to Sensitive Files

Instantly reject edits to protected files like .env or package-lock.json using exit 2.

#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

Returning exit 2 causes Claude to receive the reason for the block and try a different approach.

Re-injecting Context After Compaction

After context compaction, you can re-inform Claude of important conventions.

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Reminder: Use Bun, run bun test before committing'"
          }
        ]
      }
    ]
  }
}

Text written to stdout is added to Claude's context.

Auto-reloading Environment Variables with direnv

When changing directories, reload environment variables with direnv and have them take effect immediately in the Bash tool.

{
  "hooks": {
    "CwdChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

Content written to CLAUDE_ENV_FILE is automatically applied as a preamble when subsequent Bash commands are executed.

Exit Codes and JSON Output

Hook behavior is controlled by shell exit codes.

Exit Code Behavior
0 No objection. Normal permission flow continues
2 Blocks the action. stderr content becomes feedback to Claude
Other Non-blocking error. Execution continues but a warning appears in the transcript

Instead of exit 2 with an error message, you can also use structured output by writing JSON to stdout and returning exit 0.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Please use rg (ripgrep) instead of grep"
  }
}

permissionDecision can be "allow", "deny", or "ask". "allow" skips the interactive permission prompt, but cannot override deny rules in the configuration file.

Debugging and Caveats

Here are the checkpoints when a hook isn't working.

  1. Verify the hook appears under its event using the /hooks command
  2. Check that the script has execute permissions (chmod +x)
  3. Use claude --debug-file /tmp/claude.log to output debug logs and check match status, exit codes, and stderr
  4. If your shell profile (~/.bashrc, etc.) contains unconditional echo statements, stdout will be polluted and JSON parsing errors will occur. Guard with if [[ $- == *i* ]]; then so it only runs in interactive shells
  5. If a Stop hook blocks 8 consecutive times, it gets overridden. To prevent infinite loops, check stop_hook_active in the hook input and return exit 0 early

Hooks are powerful automation tools, but note that returning "allow" in PreToolUse cannot bypass deny rules in management policies. Conversely, "deny" can enforce blocking even in bypassPermissions mode, making it useful for policy enforcement.

For detailed input/output schemas and asynchronous hooks, refer to Anthropic's official Hooks Reference and Hooks Guide.

参考になったら ♡
Clauder Navi 編集部
@clauder_navi

Anthropic の Claude / Claude Code を中心に、日本のエンジニア向けに最新動向と実務 を毎日発信。 運営方針 は メディアについて をご覧ください。