Skip to content
InnovateTechie
Claude Code

Claude Code Hooks: Automate Your Agent Workflow

EdithBy Edith13 min read
Share
Claude Code hooks — shell commands that fire on Anthropic's coding agent lifecycle events to automate formatting, tests, and policy

Part ofWhat Is Claude Code? The Complete Guide

Quick answer

Claude Code hooks are shell commands that fire on lifecycle events — tool use, session start, stop — to format code, run tests, and block risky actions.

Claude Code hooks are user-defined shell commands that run automatically at fixed points in the agent's lifecycle — before or after a tool runs, when a session starts, or when Claude stops. They fire deterministically every time, so you can enforce policy, auto-format edits, run tests, or block risky actions without trusting the model to remember.

Hook events and exit-code behavior verified 31 July 2026 against Anthropic's Claude Code hooks reference.

That last word is the whole point. We run this site with Claude Code every day — hooks are a big part of how we get shit done with Claude Code without babysitting it — and the gap between "please run the formatter after editing" typed into a prompt and a hook that runs the formatter is the gap between usually and always. Hooks are the deterministic layer under an otherwise probabilistic agent. New to the tool? Start with our pillar, What Is Claude Code?, then come back here. Every step below was run by hand before publishing, so you are not testing our assumptions in production.

Key takeaway

Claude Code hooks are shell commands that fire deterministically on lifecycle events (PreToolUse, PostToolUse, SessionStart, Stop, and more), turning a prompt's "usually" into "always" — a PreToolUse hook that exits 2 blocks a tool call every single time.

What are Claude Code hooks?

A hook is a rule registered in a settings file: when event X happens, run command Y. Claude Code fires the event, your command runs, and its exit code decides whether the action proceeds, is blocked, or feeds text back to the model.

A hook is a rule you register in a settings file: when event X happens, run command Y. Claude Code fires the event, your command runs, and its result can let the action through, block it, or feed text back to Claude. Because a shell command either runs or it doesn't, hooks give you guarantees a prompt never can.

The mechanism has three moving parts, and they nest:

  1. The event — a lifecycle moment like PreToolUse (before a tool runs) or PostToolUse (after it succeeds).
  2. The matcher — an optional filter that narrows the event to specific tools, for example only Edit or Write calls rather than every tool.
  3. The handler — the thing that actually runs. Usually a shell command, though Claude Code also supports HTTP endpoints, single-shot prompts, and subagents.

Anthropic's official hooks reference documents every event and field, but you'll reach for a handful in daily practice.

How the hook lifecycle works

Claude Code pipes a JSON payload — session ID, working directory, tool name, tool arguments — to your handler on stdin. Exit 0 to allow, exit 2 to block and send stderr back to Claude as feedback, anything else logs a non-blocking error.

When an event fires, Claude Code passes a JSON payload to your handler on standard input — the session ID, the working directory, the tool name, and the tool's arguments. Your script reads that JSON, does its work, and signals back through its exit code:

Exit codeMeaningWhat Claude Code does
0SuccessAction proceeds; stdout can add context on some events
2Blocking errorAction is blocked; stderr is fed back to Claude as feedback
Any otherNon-blocking errorAction proceeds; the error is logged to the transcript

That exit-code-2 behavior is the single most useful thing to memorize. A PreToolUse hook that exits 2 stops the tool call cold and hands your stderr message to Claude, which then adjusts its plan. For finer control, exit 0 and print a JSON object to stdout instead — you can return a permissionDecision of deny, allow, or ask, each with a reason string Claude reads.

One caveat worth internalizing: matching hooks run in parallel, and the most restrictive answer wins. A deny from one hook doesn't cancel a sibling hook's side effects, so don't rely on one hook to suppress another.

Claude Code hook lifecycle diagram — an event fires, a matcher narrows it, JSON reaches your script on stdin, and the exit code lets the action through or blocks it

Claude Code hook events you'll actually use

Six events cover almost every real setup: PreToolUse and PostToolUse for tool calls, UserPromptSubmit for prompt screening, SessionStart for context loading, Stop for completion checks, and Notification for desktop alerts.

There are more than two dozen Claude Code hook events, from SessionStart to PreCompact to SubagentStop, and our Claude Code hooks documentation walks through every one with its config fields. Most you'll never touch. These are the ones that earn their config:

EventFiresTypical use
PreToolUseBefore a tool runsValidate or block a command; guard protected files
PostToolUseAfter a tool succeedsAuto-format, lint, or run tests on the change
UserPromptSubmitWhen you submit a promptInject context; screen the prompt before Claude sees it
SessionStartSession begins or resumesLoad environment variables or reminders into context
StopClaude finishes respondingVerify the work is actually done before it stops
NotificationClaude needs your inputDesktop alert so you can switch tasks

The matcher for tool events filters on the tool name — Bash, Edit|Write, or a regex like mcp__github__.*. For SessionStart it filters on how the session started (startup, resume, compact). Get the matcher wrong and the hook simply never fires, which is the number-one support question about Claude Code hooks.

Claude Code hooks setup: the settings.json file

Register hooks in .claude/settings.json to share them with your team, .claude/settings.local.json to keep them private, or ~/.claude/settings.json to apply them everywhere. The structure nests event, then matcher group, then handlers.

Claude Code hooks setup lives in a settings.json file, and scope depends on which file you edit:

  • .claude/settings.json — this project; commit it to share hooks with your team.
  • .claude/settings.local.json — this project, gitignored, just you.
  • ~/.claude/settings.json — every project on your machine.

The structure nests exactly the way the three moving parts do: the event name, then a matcher group, then the handlers inside it.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "npx prettier --write ..." }
        ]
      }
    ]
  }
}

Type /hooks inside a session to browse everything registered, grouped by event, with a count beside each. That menu is read-only — it shows what's live, but you edit the JSON directly or ask Claude to edit it for you. If a hook doesn't appear there, it will never run, so /hooks is the first place to look when something is off. One JSON gotcha bites everyone: a trailing comma or a stray colon silently invalidates the whole hooks block, and every hook in it vanishes at once.

Real recipes to automate Claude Code

Three hooks cover most of the value: a PostToolUse matcher on Edit|Write that pipes changed files to Prettier, a PreToolUse guard that exits 2 on protected paths, and a PostToolUse hook that runs your test suite.

Here are the three Claude Code hooks we actually run to automate Claude Code day to day. Each is short.

Auto-format every edit. A PostToolUse hook matching Edit|Write pulls the changed file path out of the JSON payload and pipes it to your formatter. This runs Prettier on anything Claude touches, so formatting never drifts:

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

Guard secrets and protected files. A PreToolUse hook that reads the target path and exits 2 when it matches .env, package-lock.json, or anything under .git/. Claude receives the stderr message and routes around the blocked file instead of clobbering it:

#!/bin/bash
FILE=$(jq -r '.tool_input.file_path // empty')
for p in ".env" "package-lock.json" ".git/"; do
  if [[ "$FILE" == *"$p"* ]]; then
    echo "Blocked: $FILE is protected" >&2
    exit 2
  fi
done

Run tests after changes. A PostToolUse hook that runs your suite after each edit turns "did it remember to test?" into a non-question — the suite runs deterministically, every time, because a hook is not a suggestion the model can skip.

A Claude Code PostToolUse hook auto-formatting an edited source file with Prettier the moment the agent saves it

For heavier setups — logging every Bash command to an audit file, re-injecting context after compaction, or blocking rm -rf outright — the ingredients are the same three parts in different arrangements. The full Claude Code CLI documentation has the complete event catalogue when you outgrow these three. Every hook still needs a session you started, though — for work that should kick off on its own clock, Claude Code's scheduled cloud routines run a saved prompt on a schedule, an API call, or a GitHub event.

Claude Code pre and post hooks: the difference

A pre hook runs before the tool executes and can cancel it with exit code 2. A post hook runs after the tool succeeds and cannot undo anything — use it for formatting, linting, tests, and cleanup.

The two events you'll configure most are Claude Code pre and post hooks, and they map cleanly to before and after:

PreToolUsePostToolUse
RunsBefore the tool executesAfter the tool succeeds
Can block?Yes — exit 2 cancels the callNo — the action already ran
Best forValidation, policy, guarding filesFormatting, linting, tests, cleanup

Use a pre hook when you need a gate: something must be checked before it happens, and blocking is on the table. Use a post hook when the action is fine but you want a guaranteed follow-up — reformat the file, run the tests, log the change. A post hook can't undo anything, because by the time it runs the edit is already on disk.

Hooks vs skills vs plugins: pick the right layer

Hooks guarantee, skills guide, plugins package. Use a hook when something must happen every time regardless of the model's judgement, a skill when you are teaching a workflow to apply when relevant, and a plugin to ship both to a team.

Claude Code hooks are one of three ways to extend the agent, and they solve different problems. Choosing wrong wastes an afternoon:

HooksSkillsPlugins
What it addsDeterministic automation on eventsProcedural knowledge — how to do a taskA bundle: skills, hooks, agents, MCP
RunsAutomatically, every matching eventWhen Claude judges a request a matchPackaged, installed as one unit
Trust modelGuaranteed — it's code, not the modelThe model chooses to apply itA distribution wrapper
Use it forFormat, test, block, log, notifyConventions, review checklists, workflowsShipping a whole setup to a team

The rule we use: hooks guarantee, skills guide, plugins package. If something must happen every time regardless of what the model decides, that's a hook. If you're teaching Claude a workflow to apply when relevant, that's a skill — our guide to Claude Code Skills covers that side, and a hook pairs naturally with a spec-driven workflow to enforce the checks a spec defines. To hand a teammate both at once, wrap them in a plugin. Hooks and skills compose well: a skill can describe the workflow while a hook enforces the non-negotiable parts. This programmable depth is a big part of what Claude Code does that Cursor can't, where rules stay suggestions rather than guarantees.

Hooks have been stable across the 2.x releases of Claude Code, and the events, matchers, and exit-code behavior described here match the current CLI.

Anthropic has documented hooks as a first-class feature since the 2.0 releases, and in our own setup every deterministic guardrail — formatting, protected-file blocks, post-edit tests — is implemented as one.

Claude pricing at a glance

Claude's Free tier costs nothing, Pro is $20 a month, Max starts at $100 a month, and API access is billed per token. Hooks themselves are free — they run locally and consume no extra tokens.

PlanPrice
Free$0
Pro$20 / month
Maxfrom $100 / month
APIPay per token

For the full breakdown of every plan, see our how much Claude costs guide.

Hooks fire on your machine; to run Claude Code on a pull request instead, our Claude Code GitHub Actions guide wires it into CI with a scoped, least-privilege token.

The most common hook people want is an alert when Claude finishes or needs approval — Claude Code notifications covers that end to end, from the zero-config terminal bell to a custom Notification hook.

Frequently Asked Questions

Claude Code hooks are user-defined shell commands, HTTP endpoints, or short LLM prompts that fire automatically at set points in the agent's lifecycle — before or after a tool runs, at session start, when Claude stops. They give deterministic control, so certain actions always happen instead of depending on the model to choose them.

Use the interactive /hooks command to browse configured hooks, then add a hooks block to a settings.json file — usually .claude/settings.json in the project root for team-shared hooks, or ~/.claude/settings.json for hooks that apply to every project on your machine. Restart if the file watcher misses the change.

PreToolUse runs before a tool executes, so it can validate or block the action — exit code 2 cancels the call. PostToolUse runs after the tool succeeds, so it suits formatting, tests, and cleanup. A post hook can't block or undo anything, because the action has already landed on disk by then.

Return exit code 2 — not 1 — from a PreToolUse hook. The tool call is cancelled and whatever you wrote to stderr is fed back to Claude as feedback, so it can adjust. For structured control, exit 0 and print JSON with a permissionDecision of deny and a reason string instead.

Check three things: the matcher spelling and case, since matchers are case-sensitive and Edit|write won't match; the settings.json location; and the JSON syntax, since a single trailing comma silently disables the whole block. Run /hooks to confirm the hook is registered under the event you expect.

Yes. Add a PostToolUse hook matching Edit|Write that runs your test suite after each edit. Because hooks execute deterministically every time rather than depending on the model to remember, the tests run on every change — exactly the guarantee a pre-commit-style safety net needs.

There are five handler types: command (a shell script), HTTP (a POST to a URL), MCP tool (a call to a connected server), prompt (a yes/no question sent to a Claude model), and agent (a subagent that inspects files before deciding). Command hooks cover the large majority of real setups.
Edith

Written by

Edith

Writing about Claude and the Anthropic toolkit — models, Claude Code, pricing, features, and fixes, in clear, practical, hands-on guides tested by daily use.

View all posts →