← Back to all guides

Hooks

─────────────────────────────────────────

Introduction

Hooks are lifecycle event handlers that run in response to things Claude does. They let you enforce rules that go beyond what CLAUDE.md instructions or settings.json permissions can do — blocking specific commands, validating tool inputs, injecting context, and reacting to session events.

If you haven’t read the earlier guides: Installing Claude Code, Using the Claude Code TUI, CLAUDE.md and Settings, Skills, Agents, and MCP Servers.

How Hooks Work

A hook is a definition in your settings.json that says “when this event happens, run this thing.” Claude Code fires events at key points in its lifecycle — before using a tool, after using a tool, when a session starts, when Claude tries to stop, and more. Your hooks listen for these events and can inspect, modify, or block what’s happening.

Hooks are defined in the same settings.json files we covered in the settings guide (user, project, local, enterprise). They follow the same cascade — all matching hooks run in parallel, and deny takes precedence.

Hook Types

There are four types of hooks, each suited to different tasks:

Command Hooks

The most common type. Runs a shell command and uses the exit code to determine the outcome.

1234
{
  "type": "command",
  "command": "bash -c 'echo \"$TOOL_INPUT\" | jq -r .command | grep -qv \"rm -rf\"'"
}

Exit codes:

  • 0 — Success. If stdout contains JSON, it’s parsed for decisions.
  • 2 — Blocking error. The stderr message is fed back to Claude as feedback.
  • Other — Non-blocking error. Logged but doesn’t stop anything.

HTTP Hooks

Sends a POST request to an endpoint. Useful for integrating with external services like logging, alerting, or approval systems.

12345
{
  "type": "http",
  "url": "https://your-service.example.com/hook",
  "allowedEnvVars": ["HOOK_AUTH_TOKEN"]
}

Prompt Hooks

A single-turn LLM evaluation. Claude assesses the situation and returns a JSON verdict. Useful for nuanced decisions that can’t be captured in a simple pattern match.

1234
{
  "type": "prompt",
  "prompt": "Check if this bash command could delete or overwrite important files. Return {\"ok\": true} if safe or {\"ok\": false, \"reason\": \"...\"} if dangerous."
}

Agent Hooks

A multi-turn sub-agent with access to Read, Grep, and Glob tools. Like prompt hooks but can investigate the codebase before making a decision. Useful for complex validations.

1234
{
  "type": "agent",
  "prompt": "Review this file edit to ensure it follows our coding conventions. Check the project's CLAUDE.md for conventions. Return {\"ok\": true} if compliant or {\"ok\": false, \"reason\": \"...\"} with specific issues."
}
Start with command hooks
Command hooks are the simplest and fastest. Start there, and only reach for prompt or agent hooks when you need the LLM to make a judgement call that can’t be expressed as a pattern match or script.

Events

Hooks fire on specific events. Here are the most useful ones:

EventWhen it firesCommon use
PreToolUseBefore Claude uses a toolBlock dangerous commands, validate inputs
PostToolUseAfter a tool runs successfullyLog actions, validate outputs
PostToolUseFailureAfter a tool failsCustom error handling
UserPromptSubmitWhen you send a messageInject context, validate prompts
StopWhen Claude tries to finishVerify task completion
SubagentStopWhen a sub-agent finishesVerify sub-task completion
SessionStartWhen a session beginsSet up environment, inject context
SessionEndWhen a session endsCleanup, logging
NotificationWhen Claude sends a notificationCustom notification routing
PreCompactBefore context compactionPreserve important information

Matchers

Most events support matchers to narrow when a hook fires. The most common is filtering by tool name:

1234567891011
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": { "toolName": "Bash" },
        "type": "command",
        "command": "bash -c 'validate-command.sh'"
      }
    ]
  }
}

This hook only fires when Claude is about to use the Bash tool, not for Read, Write, or any other tool.

Configuring Hooks

Hooks go in the hooks section of your settings.json:

.claude/settings.json
123456789101112131415161718192021
{
  "permissions": {
    "allow": ["Read(*)", "Bash(git status)"],
    "deny": ["Bash(rm -rf *)"]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": { "toolName": "Bash" },
        "type": "command",
        "command": "bash -c 'echo \"$TOOL_INPUT\" | python3 validate-bash.py'"
      }
    ],
    "Stop": [
      {
        "type": "prompt",
        "prompt": "Check if all requested tasks were completed. Return {\"ok\": true} if done or {\"ok\": false, \"reason\": \"...\"} if work remains."
      }
    ]
  }
}

Multiple hooks can listen to the same event — they all run in parallel. If any hook blocks, the action is blocked.

Hook Outputs

Hooks communicate back to Claude Code through their output. Command hooks return JSON on stdout; prompt and agent hooks return structured decisions directly.

The key output fields:

FieldEffect
continuefalse stops processing (default: true)
stopReasonMessage shown when stopping
suppressOutputHide the tool’s output from Claude
systemMessageWarning message shown to the user

For PreToolUse hooks specifically, there’s a hookSpecificOutput with:

FieldEffect
permissionDecisionallow, deny, or ask
permissionDecisionReasonExplanation for the decision
updatedInputModified tool input (lets you rewrite commands)

Practical Examples

Blocking Dangerous Commands

A PreToolUse hook that blocks destructive bash commands:

.claude/settings.json
1234567891011
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": { "toolName": "Bash" },
        "type": "command",
        "command": "bash -c 'CMD=$(echo \"$TOOL_INPUT\" | jq -r .command); echo \"$CMD\" | grep -qE \"rm -rf|DROP TABLE|truncate|format|mkfs\" && echo \"{\\\"hookSpecificOutput\\\": {\\\"permissionDecision\\\": \\\"deny\\\", \\\"permissionDecisionReason\\\": \\\"Destructive command blocked by hook\\\"}}\" || true'"
      }
    ]
  }
}

This reads the command Claude is about to run, checks it against a list of destructive patterns, and denies it if there’s a match. Unlike a settings.json deny rule which blocks based on a simple pattern, a hook can do more sophisticated validation — checking arguments, looking at the current directory, or calling external scripts.

Protecting Sensitive Files

A hook that prevents Claude from writing to sensitive files:

.claude/settings.json
1234567891011
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": { "toolName": "Write" },
        "type": "command",
        "command": "bash -c 'FILE=$(echo \"$TOOL_INPUT\" | jq -r .file_path); echo \"$FILE\" | grep -qE \"\\.(env|pem|key)$|secrets/|credentials\" && { echo \"Blocked: write to sensitive file $FILE\" >&2; exit 2; } || true'"
      }
    ]
  }
}

Exit code 2 blocks the action and feeds the stderr message back to Claude, so it knows why the write was denied and can adjust its approach.

Redirecting Commands to Better Alternatives

A hook that nudges Claude to use preferred tools:

.claude/settings.json
1234567891011
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": { "toolName": "Bash" },
        "type": "command",
        "command": "bash -c 'CMD=$(echo \"$TOOL_INPUT\" | jq -r .command); if echo \"$CMD\" | grep -q \"^grep \"; then echo \"Use rg instead of grep for better performance\" >&2; exit 2; fi'"
      }
    ]
  }
}

When Claude tries to run grep, the hook blocks it with a message suggesting rg instead. Claude reads the feedback and adjusts.

Verifying Task Completion

A Stop hook that uses an LLM prompt to verify Claude actually finished the work:

.claude/settings.json
12345678910
{
  "hooks": {
    "Stop": [
      {
        "type": "prompt",
        "prompt": "Review the conversation and check if all tasks the user requested have been completed. If any requested work is still incomplete, return {\"ok\": false, \"reason\": \"Still need to: [list remaining tasks]\"}. If everything is done, return {\"ok\": true}."
      }
    ]
  }
}

This prevents Claude from stopping prematurely — if the prompt hook decides work remains, Claude continues.

Session Startup Context

A SessionStart hook that injects useful context at the beginning of every session:

.claude/settings.json
12345678910
{
  "hooks": {
    "SessionStart": [
      {
        "type": "command",
        "command": "bash -c 'echo Current branch: $(git branch --show-current); echo Last commit: $(git log --oneline -1); echo Uncommitted changes: $(git status --porcelain | wc -l) files'"
      }
    ]
  }
}

Every time you start Claude Code, this hook prints the current git state so Claude has immediate context about where things are.

Hooks vs Settings Permissions

You might wonder when to use hooks versus the allow/deny rules in settings.json. Here’s the distinction:

  • Settings permissions are simple pattern matching. They’re fast, reliable, and good for broad rules like “never allow rm -rf” or “always allow git status.”
  • Hooks can run arbitrary logic. They can inspect the full command, check the current state of the project, call external services, or use an LLM to make nuanced decisions.

Use settings permissions as your first line of defence — they’re simpler and can’t be worked around. Use hooks for anything that needs more context or logic than a pattern match can provide.

Hooks aren't a security boundary
Command hooks run your scripts, but they’re not a tamper-proof sandbox. For hard security requirements (especially in enterprise settings), use settings.json deny rules and enterprise-level managed policies. Hooks are best for workflow enforcement, nudging, and validation — not as your only line of defence against dangerous operations.

Wrapping Up

Hooks give you programmable control over Claude Code’s behaviour at every stage of its lifecycle. Start simple — a PreToolUse hook to block a command you’re worried about, or a SessionStart hook to inject useful context. You can always add more as you discover patterns.

The most effective setups combine all three layers: settings.json permissions for broad rules, CLAUDE.md instructions for guidance, and hooks for enforcement and automation.

The next guide covers plugins and marketplaces — how to package everything we’ve covered so far into distributable, reusable units and discover plugins built by others.

Plugins and Marketplaces →