In this categoryClaude Code · 45
Claude CodeIntermediate

Claude Code Hooks Explained: Events, Config, and Blocking

How Claude Code hooks work end to end: the lifecycle events you can hook, the settings.json structure, and how a hook blocks or approves an action.

10 minIntermediate

Hooks are shell commands Claude Code runs automatically at points in its lifecycle: before a tool runs, after an edit, when you submit a prompt, when the session ends. They let you enforce rules, auto-format code, send notifications, or block dangerous actions without asking the model to remember anything. This guide is the map: which events exist, how to configure one, and how a hook can approve or block an action.

What you need

  • Claude Code installed in a project
  • Comfort editing JSON and a small shell script
  • About 10 minutes

The events you can hook

There are many hook events; these are the ones most setups actually use. Each fires at a specific moment and receives JSON about what is happening on stdin.

EventFires when
SessionStartA session begins or resumes
UserPromptSubmitYou submit a prompt, before Claude processes it
PreToolUseBefore a tool call executes (can block it)
PostToolUseAfter a tool call succeeds (for example, run a formatter)
NotificationClaude Code sends a notification
SubagentStopA subagent finishes
StopClaude finishes responding
PreCompactBefore context is compacted
SessionEndA session terminates

Step 1: Add a hook in settings.json

Hooks live in a JSON settings file: .claude/settings.json for the project, or ~/.claude/settings.json for you personally. Each event holds an array of matcher groups; each group runs one or more command handlers. The matcher filters which tools trigger the hook.

.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh"
          }
        ]
      }
    ]
  }
}

This runs format.sh after every Edit or Write. The matcher takes a single tool name, a pipe-separated list like Edit|Write, a regex, or * (or omit it) to match everything.

Step 2: Read the input in your script

The hook command receives a JSON object on stdin describing the event: the session id, the tool name, and the tool input. Parse it with jq to decide what to do.

.claude/hooks/block-rm.sh
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0
fi

Step 3: Block or approve an action

A hook controls the flow two ways. The simple way is exit codes: exit 0 allows and (optionally) prints JSON, exit 2 blocks and sends stderr back to Claude as the reason, any other code is a non-blocking error. The precise way is JSON on stdout with exit 0.

  • PreToolUse: return hookSpecificOutput with permissionDecision set to allow, deny, or ask
  • UserPromptSubmit, PostToolUse, Stop, SubagentStop: return a top-level decision of block plus a reason
  • Exit code 2 from any command hook: blocks the action, stderr becomes the reason
Hooks run with your permissions
A hook is a shell command that runs automatically with your user rights. Only add hooks you wrote or trust, keep the scripts in the repo so they are reviewable, and never put secrets in the command line where they end up in logs.

FAQ

Where should hooks be defined?

Put team-wide hooks in the project .claude/settings.json so everyone shares them, and personal hooks in ~/.claude/settings.json. Project and user hooks both run; they do not replace each other.

How do I stop a hook from blocking me?

A blocking hook uses exit code 2 or a deny decision. If a hook is getting in your way, check its script's condition, or remove its entry from settings.json. Run the script by hand with sample JSON on stdin to see exactly what it returns.

Related guides

Watch related tutorials

Free weekly email

New guides in your inbox

Fresh step-by-step how-to guides as we publish them. One email a week, no more.

Tags
#hooks#automation#settings#config#claude code