Want an AI that works for you 24/7? Get the Free Blueprint href="/blueprint">Meet your Chief AI Officer →rarr;
Claude Code Features

How to Use Hooks in Claude Code

Hooks let you intercept and extend Claude Code's behavior with custom shell commands. Set up auto-formatting, notifications, security guardrails, and workflow automations that run automatically.

Updated February 10, 2026 · 14 min read

Hooks are custom shell commands that run automatically when specific events happen in Claude Code — like auto-formatting files after every edit, blocking dangerous commands before they execute, or sending a Slack notification when a session ends. You configure them in your .claude/settings.json file using fourteen available event types.

This guide covers how to set up hooks, every available event, and practical examples for auto-formatting, security guardrails, audit logging, and workflow automation you can use today.

New to Claude Code? Watch the free CAIO Blueprint to see it in action.

What Are Claude Code Hooks?

A hook is a shell command that Claude Code executes automatically when a targeted event occurs. Think of hooks as triggers: when X happens, run Y.

When a hook event fires, Claude Code passes JSON context about the event to your hook command via stdin. Your command can read that context, do something with it, and optionally return instructions back to Claude.

Without hooks: You manually format code after every edit, approve repetitive commands, and have no visibility into what Claude does.

With hooks: Formatting happens automatically, safe commands get auto-approved, and you have complete audit logs of every action.

Available Hook Events

Claude Code provides fourteen hook events that cover the full lifecycle of a session:

Event When It Fires
SessionStart When a session begins or resumes (load context, set env vars)
UserPromptSubmit When you submit a prompt (can validate, modify, or block)
PreToolUse Before a tool executes (can block, modify input, or approve)
PermissionRequest When a permission dialog appears (can auto-approve or deny)
PostToolUse After a tool completes successfully (for formatting, logging)
PostToolUseFailure After a tool call fails (for error logging, alerts)
Notification When Claude Code sends a notification (permission, idle, auth)
SubagentStart When a subagent is spawned (inject context)
SubagentStop When a subagent finishes its task
Stop When Claude finishes responding (can force continuation)
TeammateIdle When an agent team teammate is about to go idle
TaskCompleted When a task is being marked as completed (can enforce quality gates)
PreCompact Before context compaction (manual or auto)
SessionEnd When the Claude Code session terminates

Most common hooks: Most users start with PostToolUse for auto-formatting and PreToolUse for security guardrails. These two handle 80% of use cases.

Where to Configure Hooks

Hooks are configured in your Claude Code settings files using JSON. You have three options:

File Location Scope
~/.claude/settings.json User-wide (all projects)
.claude/settings.json Project (shared with team via git)
.claude/settings.local.json Project (personal, gitignored)

For personal automations, use your user settings. For team conventions, use project settings. Hooks can also be defined in plugins (hooks/hooks.json), in skill or subagent frontmatter, and through managed enterprise policy settings.

You can also use the /hooks slash command to view, add, and delete hooks interactively without editing JSON files directly.

How to Configure Your First Hook

Let's set up a practical hook: auto-formatting code after Claude edits files.

Step 1: Open your settings file

Create or edit ~/.claude/settings.json for user-wide hooks, or .claude/settings.json in your project folder.

Step 2: Add the hooks configuration

Add a hooks object with your event and commands:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\""
          }
        ]
      }
    ]
  }
}

Step 3: Restart Claude Code

Exit and restart Claude Code for the hook to take effect. Now every time Claude edits or writes a file, Prettier runs automatically. You can also use the /hooks slash command to manage hooks interactively without editing JSON files directly.

Understanding the Hook Structure

Each hook configuration has three levels:

  1. Event: Which lifecycle event to respond to (like PreToolUse or Stop)
  2. Matcher: A regex pattern that filters when the hook fires. "Edit|Write" matches either tool. Omit the matcher or use "*" to match all occurrences.
  3. Hook handler: What to run when matched. There are three types.

Three Types of Hook Handlers

Claude Code supports three types of hook handlers:

Most examples in this guide use command hooks. Prompt and agent hooks are powerful when you need Claude's judgment to decide whether to allow or block an action.

Real-World Hook Examples

Auto-Format Code After Edits

Run Prettier (or any formatter) automatically after Claude writes or edits files:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\""
          }
        ]
      }
    ]
  }
}

Block Dangerous Shell Commands

Prevent Claude from running destructive commands like rm -rf:

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

The script reads the command from stdin and exits with code 2 to block it (exit code 2 is the blocking signal in Claude Code hooks):

#!/bin/bash
# ~/.claude/scripts/block-dangerous.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -qE 'rm -rf|rm -r /|dd if=|mkfs|:(){'; then
  echo "Blocked: dangerous command detected" >&2
  exit 2
fi

Auto-Approve Safe Commands

Automatically approve commands you trust, like running tests:

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/auto-approve.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# ~/.claude/scripts/auto-approve.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -qE '^(npm test|npm run lint|git status|git diff)'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PermissionRequest",
      decision: { behavior: "allow" }
    }
  }'
  exit 0
fi

# Don't auto-approve, let user decide
exit 0

Log All Claude Actions

Create an audit trail of everything Claude does:

{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/log-action.sh",
            "async": true
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# ~/.claude/scripts/log-action.sh
INPUT=$(cat)
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "[$TIMESTAMP] $INPUT" >> ~/.claude/audit.log

Notice the "async": true flag. This runs the logging hook in the background without blocking Claude's execution, which is ideal for logging and analytics.

Send Notifications When Done

Get notified when Claude finishes a long task:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude finished\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Linux notification alternative

On Linux, use notify-send "Claude Code" "Claude finished" instead of osascript.

Modifying Tool Inputs with Hooks

Starting in Claude Code v2.0.10, PreToolUse hooks can modify tool inputs before execution. This is powerful for enforcing conventions transparently.

Example: Automatically add dry-run flags to destructive commands:

#!/bin/bash
# ~/.claude/scripts/add-dry-run.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

# Add --dry-run to rm commands
if echo "$COMMAND" | grep -q '^rm '; then
  MODIFIED=$(echo "$COMMAND" | sed 's/^rm /rm --dry-run /')
  echo "{\"modified_input\": {\"command\": \"$MODIFIED\"}}"
  exit 0
fi

exit 0

The hook intercepts the command, modifies it, and returns the modified version. Claude proceeds with the safer command without needing to retry.

Async Hooks (Background Execution)

By default, hooks block Claude's execution until they complete. For long-running tasks like test suites, deployments, or external API calls, add "async": true to run the hook in the background while Claude continues working.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/run-tests.sh",
            "async": true,
            "timeout": 120
          }
        ]
      }
    ]
  }
}

When an async hook finishes, if it returns JSON with a systemMessage or additionalContext field, that content is delivered to Claude on the next conversation turn. Async hooks cannot block or return decisions since the triggering action has already completed.

Best for: Logging, analytics, notifications, running tests, memory storage, and any side-effect that shouldn't slow things down. Only command hooks support async; prompt and agent hooks cannot run asynchronously.

Prompt and Agent Hooks

Beyond shell command hooks, Claude Code supports two LLM-powered hook types that use Claude's judgment to make decisions:

Prompt Hooks

A prompt hook sends the hook input to a Claude model for a single-turn evaluation. The model returns a yes/no decision. Use when the hook input data alone is enough to make a decision.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete and tests pass.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

The model responds with {"ok": true} to allow or {"ok": false, "reason": "..."} to block.

Agent Hooks

An agent hook spawns a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Use when verification requires inspecting actual files or test output.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify all unit tests pass. Run the test suite and check results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Agent hooks have a longer default timeout (60 seconds vs. 30 for prompt hooks) and can perform up to 50 tool-use turns before returning a decision.

Hooks in Skills and Subagents

Hooks can also be defined directly in skills and subagents using YAML frontmatter. These hooks are scoped to the component's lifecycle and only run when that skill or agent is active:

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

For subagents, Stop hooks defined in frontmatter are automatically converted to SubagentStop events.

Common Use Cases

Auto-Formatting

Run Prettier, Black, gofmt, or any formatter after file changes. Keeps code consistent without manual intervention.

Security Guardrails

Block dangerous commands, redact secrets from outputs, enforce sandboxing rules. Prevent accidents before they happen.

Workflow Automation

Run tests after code changes, trigger builds, update documentation. Chain Claude's actions with your existing workflows.

Audit Logging

Log every action Claude takes for compliance, debugging, or learning. Build complete audit trails automatically.

Notifications

Send Slack messages, desktop notifications, or emails when Claude completes tasks or encounters errors.

Team Conventions

Enforce commit message formats, branch naming, code style. Ensure consistency across your whole team.

Security Considerations

Hooks are powerful, which means they require care:

Important: Hooks run with your user permissions. A malicious hook can do anything you can do on your machine. Only use hooks from sources you trust.

Debugging Hooks

If a hook isn't working:

  1. Check the matcher: Make sure your regex pattern matches the tool name. Use ".*" to match all tools while testing.
  2. Test the command manually: Run your hook command directly in the terminal with sample input to verify it works.
  3. Check file permissions: Make sure your hook script is executable (chmod +x script.sh).
  4. Look for JSON errors: If your hook returns JSON, make sure it's valid. Use jq to validate.
  5. Check Claude Code debug output: Run claude --debug to see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode with Ctrl+O in a session.
  6. Hooks capture at startup: Claude Code captures a snapshot of hooks when a session starts. If you edit hooks in settings files mid-session, Claude Code warns you and requires review in the /hooks menu before changes apply.

Common Mistakes

  1. Forgetting to make scripts executable: Run chmod +x on your hook scripts.
  2. Invalid JSON in settings: One misplaced comma breaks everything. Use a JSON validator.
  3. Matcher too broad: A ".*" matcher runs on every tool. Be specific when possible.
  4. Blocking without feedback: If a PreToolUse hook blocks an action, return an error message explaining why.
  5. Not handling missing fields: Your hook might receive different JSON structures. Check fields exist before using them.

FAQ

Can hooks break Claude Code?

A failing hook won't crash Claude Code, but it might block actions or cause unexpected behavior. Test hooks carefully and check logs if things seem wrong.

Do hooks run for subagents too?

Yes. Hooks fire for tool calls from both the main agent and subagents. The SubagentStop event specifically fires when a subagent completes.

Can I have multiple hooks for the same event?

Yes. Hooks are defined in an array, so you can have multiple hooks for the same event with different matchers. They run in order.

How do I disable a hook temporarily?

Set "disableAllHooks": true in your settings file to disable all hooks at once, or use the toggle in the /hooks menu. To remove a single hook, delete its entry from the JSON file or select it in the /hooks menu and delete it.

Can hooks communicate with each other?

Not directly, but they can read and write to shared files. Use a temp file or a simple database like SQLite to pass data between hooks.

Related Guides

Like Claude Code? Meet Your Chief AI Officer

Watch me build a complete website using only plain English - no coding required. Then try it yourself.

Get the Free Blueprint href="/blueprint">Watch the Free Setup Video →rarr;