Launch price: 120+ Claude Code agents for $49 one-time · lifetime updatesGet access →
Coding·Updated ·9 min read

Claude Code Hooks: The Complete Guide (with Copy-Paste Examples)

Instructions in CLAUDE.md are suggestions — Claude usually follows them, but nothing forces it to. Hooks are different. A hook is a command that Claude Code runs automatically at a set moment, such as before a tool runs or after a file is edited, and it can block an action outright.

This guide covers the events you'll actually use, how to configure hooks, and four practical examples.

The events you'll use most

Claude Code has many hook events. These cover most real-world needs:

  • PreToolUse — before a tool call runs; can block it (e.g. stop rm -rf or edits to .env)
  • PostToolUse — after a tool call succeeds (e.g. format a file Claude just edited)
  • UserPromptSubmit — when you send a prompt, before Claude sees it (e.g. add context)
  • Notification — when Claude Code sends a notification (e.g. needs your permission)
  • Stop — when Claude finishes responding (e.g. play a sound, run tests)
  • SubagentStart / SubagentStop — when a subagent begins or ends
  • SessionStart / SessionEnd — when a session starts, resumes or ends
  • PreCompact / PostCompact — around context compaction

Where hooks are configured

  • ~/.claude/settings.json — all your projects
  • .claude/settings.json — this project, shared with your team
  • .claude/settings.local.json — this project, just you (gitignored)
  • Skill or subagent frontmatter — only while that skill or agent is active

Run /hooks inside Claude Code to see the hooks that are currently configured.

How a hook is structured

Hooks are nested three levels deep: the event, a matcher that filters when it fires (for tool events, the tool name), and the commands to run.

.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "/path/to/your-script.sh" }
        ]
      }
    ]
  }
}

Your command receives JSON on stdin with fields like session_id, cwd, hook_event_name, and for tool events tool_name and tool_input. Exit code 0 means carry on. Exit code 2 blocks the action on events that can block, and whatever you print to stderr is shown to Claude as the reason. Other exit codes are treated as a non-blocking error — so use exit 2, not exit 1, to enforce a rule.

Example 1: auto-format every file Claude edits

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

This reads the edited file's path from the hook input with jq and runs Prettier on it. Swap in black, gofmt or your formatter of choice.

Example 2: block dangerous shell commands

.claude/hooks/block-dangerous.sh
#!/bin/bash
cmd=$(jq -r '.tool_input.command // ""')
if echo "$cmd" | grep -Eq 'rm -rf /|git push --force|DROP TABLE'; then
  echo "Blocked by project hook: $cmd" >&2
  exit 2
fi
exit 0

Register it under PreToolUse with the matcher Bash. Because it exits with code 2, Claude Code stops the command and tells Claude why.

Example 3: protect secrets files

.claude/hooks/protect-env.sh
#!/bin/bash
path=$(jq -r '.tool_input.file_path // ""')
case "$path" in
  *.env|*.env.*|*secrets*)
    echo "Editing $path is not allowed" >&2
    exit 2 ;;
esac
exit 0

Register it under PreToolUse with the matcher Edit|Write.

Example 4: get notified when Claude needs you (macOS)

~/.claude/settings.json
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your input\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Tips for safe, fast hooks

  • Keep hooks quick — they run on every matching event
  • Use narrow matchers so a formatter doesn't run on every tool call
  • Test scripts by piping sample JSON into them before registering
  • Remember hooks run with your permissions — review any hook before adding it to a shared repo
  • Use hooks for rules that must always hold; keep preferences in CLAUDE.md

Frequently asked questions

What are Claude Code hooks?

Commands that Claude Code runs automatically at set points, such as before or after a tool runs, when you submit a prompt or when Claude stops. They can block actions.

How does a hook block an action?

On events that can block, such as PreToolUse, exit with code 2. Claude Code stops the action and shows your stderr message to Claude as the reason.

Where do I add hooks?

In a settings file: ~/.claude/settings.json for all projects, .claude/settings.json for a shared project setting, or .claude/settings.local.json just for you.

Hooks or CLAUDE.md — which should I use?

CLAUDE.md for guidance Claude should follow; hooks for rules that must always be enforced, like formatting or blocking dangerous commands.

Skip the setup: get 120+ ready-made agents

Coding, SEO and marketing kits for Claude Code, with 54 skills and 54 slash commands. $49 one-time, lifetime updates.

See what's included →

Keep reading