All posts
Developer Tools·8 min read·Updated 23 Oct 2026

Claude Code Hooks: Working Examples and How Exit Code 2 Blocks Actions (2026)

Quick answer
Claude Code hooks are shell commands that run at fixed lifecycle points such as PreToolUse, PostToolUse and Stop. Configure them in .claude/settings.json. A hook that exits with code 2 blocks the action and sends its stderr to Claude as feedback; exit 0 means no objection.

Practical Claude Code hooks: auto-format after edits, block edits to protected files, notifications, context after compaction. Events, matchers, settings locations and exit codes explained.

Piyush Jangir
Verified author

Founder of StackPicks. Self-taught builder shipping open-source dev tools, marketing, and curator content since 2019. Based in Mumbai, India. Available on GitHub and LinkedIn.

8 min read
Claude Code Hooks: Working Examples and How Exit Code 2 Blocks Actions (2026)

**Short version:** hooks turn "please always run the formatter" from a hope into a guarantee. The model can forget an instruction; a hook cannot, because Claude Code runs it, not Claude. Everything below is from Anthropic's hooks guide — the two code examples are quoted from it.

The Claude Code hook lifecycle from SessionStart to Stop

Where hooks live

FileScopeShareable
~/.claude/settings.jsonAll your projectsNo
.claude/settings.jsonOne projectYes, commit it
.claude/settings.local.jsonOne projectNo
Plugin hooks/hooks.jsonWhen the plugin is enabledYes
Skill or subagent frontmatterWhile that skill or subagent runsYes

Run /hooks inside Claude Code to browse every configured hook grouped by event. To switch them all off, set "disableAllHooks": true in a settings file.

The events worth knowing first

  • `PreToolUse` — before a tool call runs. Can block it.
  • PostToolUse — after a tool call succeeds.
  • UserPromptSubmit — when you submit a prompt, before Claude sees it.
  • Notification — when Claude Code sends a notification.
  • Stop — when Claude finishes responding.
  • SessionStart — when a session begins or resumes.
  • PreCompact / PostCompact — around context compaction.
  • FileChanged — when a watched file changes on disk.

There are many more, including SubagentStart, SubagentStop, PermissionRequest and SessionEnd.

Example 1: format every file Claude edits

A PostToolUse hook with an Edit|Write matcher runs only after file-editing tools. Add to .claude/settings.json:

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

The hook receives the tool call as JSON on stdin; jq pulls out the file path. You need jq installed.

Example 2: block edits to protected files

Save as .claude/hooks/protect-files.sh, make it executable, and register it as a PreToolUse hook:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0

Because it exits 2, Claude receives the stderr message and adjusts instead of silently failing.

How exit codes decide what happens

Claude Code hook exit codes: 0 allows, 2 blocks, others usually proceed

  • Exit 0 — no objection; the action proceeds.
  • Exit 2 — block. Write the reason to stderr. Depending on the event, Claude gets it as feedback, you see it, or nothing appears. Some events, such as SessionStart, cannot be blocked.
  • Any other code with no valid JSON — the action usually proceeds, unless the hook is configured so its failure blocks.

Pick one style per hook: exit 2 with stderr, or exit 0 with JSON output for structured control.

Matchers in one table

EventsMatcher filters onExample values
PreToolUse, PostToolUsetool nameBash, `Edit\Write, mcp__.*`
SessionStarthow it startedstartup, resume, compact
Notificationnotification typepermission_prompt, idle_prompt
FileChangedfilenames to watch`.envrc\.env`

The `compact` matcher on `SessionStart` is how you re-inject key context after compaction; `mcp__.*` lets you police every MCP tool call — useful alongside our MCP security checklist.

**Related:** Claude Code plugins · Claude Code subagents · Reduce Claude Code token usage

Frequently asked questions

What are hooks in Claude Code?+

Hooks are user-defined shell commands that Claude Code runs at specific points in its lifecycle, such as before a tool call, after a file edit, or when Claude finishes responding. They give deterministic control: an action always happens instead of depending on the model remembering an instruction. Common uses are auto-formatting edited files, blocking edits to sensitive files, sending desktop notifications and re-injecting context after compaction.

Where do I configure Claude Code hooks?+

Add a hooks block to a settings file. Use ~/.claude/settings.json for hooks that apply to all your projects, .claude/settings.json for project hooks you want to commit and share, or .claude/settings.local.json for project hooks that stay on your machine. Hooks can also ship inside a plugin's hooks/hooks.json or in skill and subagent frontmatter. Run /hooks inside Claude Code to see everything configured.

How do I block a tool call with a hook?+

Use a PreToolUse hook and exit with code 2. Write the reason to stderr; for tool calls, Claude receives that message as feedback and can change its approach. A typical pattern is a script that reads the tool call JSON from stdin, extracts the target file path with jq, and exits 2 if it matches protected patterns such as .env or the .git directory. Exit 0 means the action may proceed.

What does exit code 2 mean in a Claude Code hook?+

Exit code 2 tells Claude Code to block the action and treat stderr as the reason. What happens to that message depends on the event: Claude may receive it as feedback, you may see it, or for some events nothing appears. A few events cannot be blocked at all; for SessionStart, exit 2 just shows stderr and execution continues. Other non-zero codes without JSON output usually let the action proceed.

Can Claude Code hooks run on MCP tool calls?+

Yes. PreToolUse and PostToolUse matchers filter on the tool name, and MCP tools are named with an mcp__ prefix, so a matcher such as mcp__.* catches every MCP tool call. That makes hooks a practical way to log, audit or block calls to connected MCP servers, for example refusing write operations from a server you only trust for reads. Test matchers carefully, since an overly broad pattern can block legitimate work.

More in Developer Tools

Claude Code Hooks: Working Examples and How Exit Code 2 Blocks Actions (2026) — StackPicks — StackPicks