Deepseek ArtifactsDeepseek Artifacts
Official-hooks sourced guide

Claude Code Hooks: settings.json, the 5 Events & Exit-Code Blocking

Hooks are the deterministic layer of Claude Code: they always run, no prompting required. This illustrated walkthrough uses Anthropic's own hooks video for the visuals — the five events, a PreToolUse block script, structured deny JSON, and a full PostToolUse formatting config.

TL;DR — what Claude Code hooks are

  • Hooks are deterministic: they run at fixed points in Claude Code's lifecycle every single time. A CLAUDE.md instruction like “run Prettier after every edit” works most of the time — a hook works always.
  • There are five events: UserPromptSubmit (before your prompt is processed), PreToolUse (before a tool call), PostToolUse (after a tool completes), Notification, and Stop (when Claude finishes responding).
  • A PreToolUse hook that exits with code 2 blocks the tool call, and the stderr message is fed back to Claude so it knows why. Exit code 0 lets the call proceed.
  • Hooks live in settings.json — an event, an optional tool matcher, and a command. Keep them in the project's .claude/settings.json and commit it, and your whole team inherits the same guarantees.

Hooks in Claude Code

Channel:Claude (official Anthropic channel)3:22

Watch

Claude Code Hooks, Explained Simply

Channel:Agentic Lab8:32

Watch

Claude Code - Getting Started with Hooks

Channel:Greg Baugues11:53

Watch

Hooks reference — Claude Code documentation

Docs:code.claude.com

Watch

Frames in this guide come from Anthropic's official hooks explainer; the walkthrough text was written independently and cross-checked against the official hooks reference.

Screenshots remain the property of their creators and are used with attribution as visual documentation. Each step deep-links back to the exact moment in the source video.

Set up Claude Code hooks, step by step

1 · What hooks look like in the wild

  1. 1

    Watch a hook fire at the end of a response

    The status line reads “Running stop hook · 39s · 484 tokens” — Claude Code is executing a Stop hook before it hands the turn back to you. That is the whole idea in one screenshot: a command you registered runs at a fixed point in the lifecycle, on every matching occurrence, with no reliance on the model remembering to do something.

    Claude Code terminal showing a running Stop hook at 39 seconds with 484 tokens right after Claude finished composing an answer
    A Stop hook executing after Claude's answer — 39 seconds in, 484 tokens spent.Watch at 0:10
  2. 2

    Learn the five hook events

    UserPromptSubmit runs the moment you submit a prompt, before Claude processes it. PreToolUse runs before each tool call. PostToolUse runs after a tool call completes. Notification fires when Claude sends a notification, and Stop runs when Claude finishes responding. Every hook you write attaches to exactly one of these five points.

    Slide listing the five Claude Code hook events UserPromptSubmit, PreToolUse, PostToolUse, Notification and Stop from Anthropic’s official hooks tutorial
    The five events, from Anthropic's official hooks video — everything else hangs off this list.Watch at 1:04

2 · Write your first hooks

  1. 3

    Add a hooks block to settings.json

    A hook is three things in settings.json: the event name, an optional matcher that narrows which tool it applies to, and the command to run. In the screenshot the PreToolUse matcher is being completed with Edit — that hook will only fire on file-edit tool calls. The /hooks menu edits the same config interactively if you'd rather not touch JSON.

    Claude Code settings.json with a PreToolUse hooks array open in VS Code while the matcher field autocompletes Edit for a tool-scoped hook
    The matcher autocompletes Edit, scoping a PreToolUse hook to file edits only.Watch at 0:14
  2. 4

    Block dangerous commands with exit code 2

    A PreToolUse hook receives the tool name and its input as JSON on stdin. This script pipes it through jq to grab .tool_input.command, greps for destructive patterns — rm -rf, git push --force — and on a match echoes the reason to stderr and exits 2. Exit code 2 blocks the call; the stderr text is fed back to Claude as feedback, so the model knows why it was blocked and can adjust.

    Bash PreToolUse hook script using jq to read tool_input.command from stdin and exit 2 to block destructive rm -rf and git push --force commands in Claude Code
    jq reads the command from stdin; a match on rm -rf or --force prints to stderr and exits 2.Watch at 2:02
  3. 5

    Send a structured denial instead of an exit code

    For finer control, a hook can print a JSON decision instead of relying on exit codes. Here a PreToolUse hook catches DROP TABLE, and the hookSpecificOutput carries permissionDecision “deny” plus a reason — “use a migration instead” — that lands in the model's context. Same hard guarantee, but with an actionable instruction attached.

    Claude Code PreToolUse hook denying a DROP TABLE SQL command with hookSpecificOutput permissionDecision deny JSON that tells the model to use a migration instead
    A permissionDecision of deny blocks the SQL command and tells the model what to do instead.Watch at 2:16
  4. 6

    Keep hooks in the repo so the team gets them

    Hooks configured in the project's .claude/settings.json are project-level and can be committed. Everyone who clones the repo runs the same hooks automatically — including the blocking ones. Store helper scripts in .claude/hooks/ and reference them with the CLAUDE_PROJECT_DIR environment variable so the paths resolve no matter where Claude's current working directory is.

    VS Code explorer showing a project .claude folder with a hooks directory and settings.json open next to CLAUDE.md for team-shared Claude Code hooks
    The project's .claude folder holds settings.json plus a hooks/ directory of shared scripts.Watch at 0:17
  5. 7

    Copy a complete, real hooks config

    This config does two jobs at once. The PostToolUse block matches Edit|Write|MultiEdit and runs .claude/hooks/auto-format.sh with a 30-second timeout, so every file Claude touches gets formatted. Below it, a second hook matches Bash and logs every executed command — the compliance pattern. The timeout and async fields keep slow formatters from stalling the session.

    settings.json hooks block with a PostToolUse matcher of Edit|Write|MultiEdit running an auto-format.sh script at timeout 30 plus a Bash command logging hook
    PostToolUse auto-formatting with a 30s timeout, plus a Bash hook logging every command.Watch at 2:46

3 · Run them like a team

  1. 8

    Know the exit-code contract cold

    Exit code 0 means proceed. Exit code 2 means block — and stderr is fed to Claude as feedback it can act on. Any other exit code shows stderr to you, the user, but the tool call continues; use that for warnings you want to see without hard-blocking the agent.

  2. 9

    Register hooks from the /hooks menu and pick your recipes

    The /hooks command opens the same configuration interactively — useful for verifying which hooks are registered at which scope. From here the four workhorse recipes: auto-format after edits (PostToolUse), log all executed commands (PostToolUse on Bash), block dangerous operations (PreToolUse with exit 2), and send yourself a notification when Claude finishes (Stop). If something must happen every time without fail, don't put it in a prompt — put it in a hook.

The exit-code contract, in one table

Every hook command communicates through its exit code. Three cases cover everything you need:

  • exit 0Proceed. The tool call runs as normal. stdout from the hook is visible in transcript mode (Ctrl-R).
  • exit 2Block. The tool call is rejected and the hook's stderr is fed back to Claude as feedback, so the model can correct course — that's what makes exit-2 hooks teachable rather than just fatal.
  • exit 1Any other code: warn, don't block. stderr is shown to you but the tool call continues. Use it for advisory hooks — “this file is usually generated, are you sure?”

One more upgrade path: instead of exit codes, a hook can print a JSON decision (hookSpecificOutput with permissionDecision) to deny with a structured reason, as in step 5. Exit codes are the simple contract; JSON decisions are the typed one.

Four recipes worth committing today

The official video calls out four use cases; here they are as copy-paste intent:

  1. 1Auto-format after edits — a PostToolUse hook matching Edit|MultiEdit that checks the file extension and runs the right formatter: Prettier for TypeScript, gofmt for Go, Ruff for Python.
  2. 2Log every executed command — a PostToolUse hook on Bash appending each command to a file. Compliance teams love this one; future-you debugging last Tuesday will too.
  3. 3Block dangerous operations — a PreToolUse hook with exit 2 guarding production config directories, rm -rf patterns, or commits to main. These become guarantees, not suggestions.
  4. 4Notify when the task finishes — a Stop or Notification hook that triggers a desktop notification or sound, so long agent runs don't need babysitting.

All four fit in one .claude/settings.json. Start with the formatter — it's the hook you'll feel every single save.

Claude Code hooks FAQ

Related guides