Claude Code Hooks Cheat Sheet

This page is a condensed reference for Claude Code hooks: the settings files they live in, the lifecycle events you can hook into, matchers, the JSON payload your script reads, the exit-code contract that blocks or steers Claude, and ready-to-use Python hooks that auto-format edits, guard commands, and notify you when Claude stops. You can also download the information as a printable cheat sheet:

Free Bonus: Claude Code Hooks Cheat Sheet

Get a Claude Code Hooks Cheat Sheet (PDF) with the settings layout, lifecycle events, exit-code contract, and copy-paste hook scripts on one page:

Claude Code Hooks Cheat Sheet

Practice what you learn with hands-on coding exercises, quizzes, and guided learning paths. Not sure where to begin? Start here.

New to Claude Code hooks?

Hook Anatomy

  • A hook is a command that runs on an event, filtered by a matcher
  • It fires every time, no matter what the model decides
  • Omit matcher or use "*" to match every tool
  • "Bash" matches one tool, "Write|Edit" several, "^Notebook" is a regex
  • Hooks merge across files; commit .claude/settings.json to share them
Settings file Scope
~/.claude/settings.json All your projects
.claude/settings.json This project, shareable
.claude/settings.local.json This project, gitignored
Language: JSON Filename: Register a Hook
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "uv run .claude/hooks/guard_pip.py"
          }
        ]
      }
    ]
  }
}

Using more than one settings file?

Lifecycle Events

  • Exit 2 can block PreToolUse, UserPromptSubmit, Stop, and PreCompact
  • PostToolUse can’t block; the tool already ran
  • Tool hooks also fire inside subagents (agent_id is set)
Event Fires
SessionStart A session begins or resumes
UserPromptSubmit You submit a prompt
PreToolUse Before a tool call runs
PostToolUse After a tool call succeeds
Notification Claude Code needs your attention
Stop Claude finishes a response
PreCompact Before context compaction
SessionEnd The session terminates

Know when each event fires?

Read the Event

  • The event arrives as JSON on standard input
  • Always present: hook_event_name, session_id, cwd
  • Tool events add tool_name and tool_input
Language: Python Filename: Parse the Payload
import json
import sys

event = json.load(sys.stdin)
event["hook_event_name"]  # 'PreToolUse'
event["tool_name"]        # 'Bash'
event["tool_input"]       # {'command': 'pip install rich'}

Want to go deeper on reading stdin?

Exit-Code Contract

  • exit 1 does not block. Only 2 does
  • Steer, don’t just deny: tell Claude what to run instead
  • Stderr on exit 0 goes to the debug log only; Claude never sees it
  • A hook that hits its timeout (600 s default) is discarded; the action proceeds
Exit code Effect
0 Proceed; stdout may carry JSON
2 Block; stderr goes to Claude as feedback
Other Non-blocking error; the action proceeds
Language: Python Filename: Block pip, Suggest uv
import json
import sys

event = json.load(sys.stdin)
cmd = event.get("tool_input", {}).get("command", "")
if cmd.startswith(("pip ", "pip3 ")):
    print("Use 'uv add' instead of pip.", file=sys.stderr)
    sys.exit(2)  # Blocked; Claude reads the message
sys.exit(0)  # Allowed
Language: Shell Filename: Test the Guard Without Claude
$ echo '{"tool_name": "Bash", "tool_input":
  {"command": "pip install rich"}}' \
  | uv run .claude/hooks/guard_pip.py; echo "exit $?"
Use 'uv add' instead of pip.
exit 2

Think you’ve got the contract down?

Auto-Format Every Edit

  • Match "Write|Edit" on PostToolUse
  • Hooks inherit your shell PATH, not the project’s virtual environment
  • Run ruff check --fix before ruff format
  • PostToolUse also sees tool_response, the tool’s result
  • Files rewritten by a Bash command don’t trigger Write|Edit hooks
Language: Shell Filename: Put Ruff on Your PATH
$ uv tool install ruff
Installed 1 executable: ruff
Language: Python Filename: Format the Changed File
import json
import subprocess
import sys
from pathlib import Path

event = json.load(sys.stdin)
path = event.get("tool_input", {}).get("file_path", "")
if path.endswith(".py") and Path(path).exists():
    subprocess.run(["ruff", "check", "--fix", path])
    subprocess.run(["ruff", "format", path])

Formatter not running?

Notify When Claude Stops

  • Register under "Stop", which fires after every response, not just the last
Language: Python Filename: Desktop Notification
import json
import platform
import subprocess
import sys

json.load(sys.stdin)  # Consume the event
msg = "Claude just finished responding"
if platform.system() == "Darwin":
    note = f'display notification "{msg}"'
    cmd = ["osascript", "-e", note]
else:
    cmd = ["notify-send", "Claude Code", msg]
try:
    subprocess.run(cmd)
except FileNotFoundError:
    print(msg)  # No notifier installed? Print instead

Want to go deeper on subprocess?

Structured JSON Replies

  • Exit 0 and print one JSON object for finer control
  • Stop and PostToolUse: {"decision": "block", "reason": "..."}
Language: Python Filename: Allow, Deny, or Ask on PreToolUse
import json

reply = {"hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask",  # One of allow, deny, ask
    "permissionDecisionReason": "This edits prod config",
}}
print(json.dumps(reply))

Exit code or JSON?

Troubleshooting

  • Not firing? Check the event name, the matcher, and the JSON commas
  • /hooks lists what actually loaded and from which file
Language: Shell Filename: Trace a Hook in the Debug Log
$ claude --debug-file /tmp/hooks-debug.txt
$ grep -A2 "Hook" /tmp/hooks-debug.txt
[DEBUG] Hook PreToolUse:Bash (PreToolUse) error:
Use 'uv add' instead of pip.
[DEBUG] Hook denied tool use for Bash

Hook not firing?

Ready to go beyond the cheat sheet?

You can download this information as a printable cheat sheet:

Free Bonus: Claude Code Hooks Cheat Sheet

Get a Claude Code Hooks Cheat Sheet (PDF) with the settings layout, lifecycle events, exit-code contract, and copy-paste hook scripts on one page:

Claude Code Hooks Cheat Sheet