✻Founding-member pricing: every lesson for $19/mo. The first 3 lessons are free. Start lesson 1 →
Unit 7 · Hooks & Enforcement · Lesson 30 of 40

Hooks Fundamentals

Learn Claude Code hooks: lifecycle events like PreToolUse and Stop, the settings.json structure, matchers, stdin JSON input and exit-code semantics.

ACADEMY12 min+74 XP5-question quiz

🎯 By the end of this lesson you'll be able to

  • Explain why hooks are enforcement, not guidance
  • Name the key lifecycle events
  • Read and write the hooks configuration shape
  • Use exit codes and JSON output to allow, block or add context

Requests vs. guarantees

"Never edit .env" in CLAUDE.md is a request: Claude will almost always follow it, but almost isn't good enough for some rules. A PreToolUse hook that blocks edits to .env is a guarantee. Hooks are commands Claude Code runs automatically at specific points in its lifecycle. Claude doesn't decide whether they run. They always do.

They also cost zero context unless they return output.

The events you'll use most

EventFires whenClassic use
SessionStartA session starts or resumesLoad dynamic context (today's tickets, env info)
UserPromptSubmitYou submit a promptAdd context, or block certain prompts
PreToolUseBefore a tool runsBlock dangerous actions
PostToolUseAfter a tool succeedsFormat, lint or run quick checks
NotificationClaude needs your attentionDesktop or phone alerts
StopClaude finishes its turnVerify work before it can stop
SubagentStopA subagent finishesCheck subagent results
PreCompact / PostCompactAround compactionSave or restore key context
SessionEndThe session endsCleanup and logging

There are many more (permission requests, file changes, worktrees, config changes), but these cover most real-world needs.

The configuration shape

Hooks live in settings files under a hooks key: event → matcher groups → handlers.

.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "npx prettier --write --ignore-unknown \"$(jq -r '.tool_input.file_path')\"" }
        ]
      }
    ]
  }
}
  • **matcher** filters which occurrences fire. For tool events it's the tool name: Bash, Edit|Write or mcp__github__.*. An empty matcher matches everything.
  • **type** is usually command (a shell command). Others include http (POST the event to a URL), mcp_tool, prompt (a quick model check) and agent.

Input: JSON on stdin

Your command receives event details as JSON on stdin, including session_id, cwd, hook_event_name, tool_name and tool_input. Use jq to pull out what you need:

bash
jq -r '.tool_input.file_path'   # the file being edited
jq -r '.tool_input.command'     # the shell command about to run

Output: exit codes and decisions

Exit codeMeaning
0Success, no objection. For some events, stdout is added as context.
2Block. stderr is sent to Claude as the reason, and it adapts.
OtherA non-blocking error. The action usually proceeds.
!
Exit 0 isn't approval

For PreToolUse, exiting 0 means "no objection". The normal permission flow still applies. Hooks can deny, but a silent success doesn't grant permission.

For finer control, exit 0 and print JSON. For example, a PreToolUse decision:

json
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Production config is read-only" } }

Where hooks can be defined

User settings, project settings, local settings, managed policy, plugins, and even skill or subagent frontmatter (active while they run). Hooks from every source merge, and all matching hooks run. Use /hooks to browse what's active.

⇄Flashcards4 cards

Check your understanding

?Lesson quiz10 XP per correct answer · +25 for a perfect score
QUESTION 1 OF 5

Why use a hook instead of a CLAUDE.md line for a critical rule?

Why: CLAUDE.md is guidance. Hooks are enforcement.
QUESTION 2 OF 5

Which event can block a tool before it runs?

Why: PreToolUse runs first and can block with exit code 2.
QUESTION 3 OF 5

What does exit code 2 mean for a blocking hook?

Why: Exit 2 blocks and feeds your message back so Claude can adapt.
QUESTION 4 OF 5

How does a hook command receive event details?

Why: Event data arrives as JSON on stdin, and jq is your friend.
QUESTION 5 OF 5

Does exiting 0 from a PreToolUse hook approve the tool call?

Why: Hooks can deny, but silence isn't approval.
0 / 5 correct
Academy lesson

Unlock lesson 30 and the next 10

You've finished the free lessons. Join the Academy for $19/month to unlock every lesson, quiz, terminal simulation and puzzle, plus the final exam and your certificate of completion.

  • All 40 lessons across 9 units
  • 165+ quiz questions
  • Final exam + certificate
  • New lessons as Claude Code evolves
  • XP, streaks and badges
  • Cancel anytime

Already a member? Log in to continue.

Key takeaways

  • Hooks are deterministic: they always run and cost no context unless they output.
  • Shape: event → matcher → handlers, in settings.json.
  • Input is JSON on stdin. Exit 2 blocks, and stderr explains why.
  • Hooks merge across all sources. Inspect them with /hooks.

Nice work. Lesson 30 done?

Mark it complete to earn +74 XP, keep your streak alive and unlock badges.

Written by Curtis Cornell from Anthropic's public Claude Code documentation. Last reviewed November 14, 2026. Claude Code changes fast; if something here differs from your version, the official docs win.

Start lesson 1 free →