Skip to content
InnovateTechie
Claude Code

Claude Code Hooks: Automate Your Agent Workflow

InnovateTechieBy InnovateTechie10 min read
Share
Claude Code Hooks: Automate Your Agent Workflow

Part ofWhat Is Claude Code? The Complete Guide

Claude Code hooks are shell commands that fire on lifecycle events — before/after 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.

That last word is the whole point. We run this site with Claude Code every day, 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.

What are Claude Code hooks?

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 Claude Code hooks work

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

There are more than two dozen Claude Code hook events, from SessionStart to PreCompact to SubagentStop. 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

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

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.

Claude Code pre and post hooks: the difference

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

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. 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.

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.

According to Claude Code's official documentation, hooks have been a first-class feature since the 2.0 releases, and roughly 100% of the deterministic guardrails we rely on are built on them.

Claude pricing at a glance

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.

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.
InnovateTechie

Written by

InnovateTechie

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 →