← Back to Blog

Claude Code Hooks: Complete Guide (2026)

Claude Code hooks explained: every event type, settings.json config, matcher patterns, blocking exit codes, and real automation examples.


Claude Code hooks let you run your own shell commands at fixed points in an agent's lifecycle — before a tool runs, after it finishes, when a session starts, when Claude wants your attention. They turn "I keep telling Claude to do X every time" into a rule the harness enforces automatically, without spending a single token of context on the instruction.

If you have ever asked an agent nicely not to run rm -rf, or pasted a linter command into a prompt for the tenth time, hooks are the fix. This guide covers every hook event, how to configure them, and working examples for the automations teams actually run in production.

What a Hook Actually Is

A hook is a shell command you register against a lifecycle event. When that event fires, Claude Code runs your command and passes it structured JSON on stdin — session id, transcript path, the event name, and (for tool events) the tool name and its input. Your command's exit code and output decide what happens next: let the action proceed, block it, or just log it and move on.

This is different from a system prompt instruction. A prompt is a request the model can misread, forget under context pressure, or deprioritize against competing instructions. A hook is code — it runs the same way every time, with no dependence on the model choosing to comply.

Where Hooks Live: settings.json

Hooks are configured in a hooks block inside settings.json — at the user level (~/.claude/settings.json) for rules that apply everywhere, or the project level (.claude/settings.json) for rules scoped to one repo. Project-level hooks are the right choice for anything team-specific: a linter, a build check, a security gate that should apply to everyone working in that codebase.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "~/.claude/scripts/check-dangerous-commands.sh" }
        ]
      }
    ]
  }
}

The Hook Events

Each event fires at a distinct point in the agent loop:

  • PreToolUse — fires before a tool call executes. The only event that can block the action outright.
  • PostToolUse — fires after a tool call completes. Good for formatting, logging, or triggering a follow-up check based on the result.
  • UserPromptSubmit — fires when the user submits a message, before Claude processes it. Useful for injecting context or rejecting a prompt outright.
  • Notification — fires when Claude Code sends a notification (e.g. waiting on permission, needs input). Wire this to a desktop alert or a Slack ping.
  • Stop — fires when the main agent loop finishes responding. Good for a final validation pass or a completion log.
  • SubagentStop — fires when a delegated subagent (via the Agent tool) finishes its task.
  • SessionStart — fires once when a new session boots. Common use: load project context, warm a cache, print a status banner.
  • PreCompact — fires right before the harness compacts the conversation to free up context, letting you snapshot state first.

Matchers: Scoping a Hook to Specific Tools

Tool-related events (PreToolUse, PostToolUse) support a matcher field — a regex tested against the tool name. "matcher": "Bash" only fires on Bash calls. "matcher": "Edit|Write" fires on either. Omit the matcher to run the hook on every tool call for that event, which is expensive if your hook does real work — scope it.

The Payload and Exit Codes

Your hook command receives a JSON object on stdin. For a PreToolUse hook on Bash, that includes the session id, the tool name, and tool_input.command — the exact command Claude is about to run. Parse it, decide, and respond through your exit code:

  • Exit 0 — approve. The action proceeds. Anything written to stdout is shown to the user but does not affect Claude.
  • Exit 2 — block (PreToolUse only). Stderr is fed back to Claude as the reason, so it can adjust and retry with something safer.
  • Any other non-zero exit — treated as a hook error. Shown to the user, but does not block the underlying action for events that don't support blocking.

Real Examples

Block dangerous shell commands

A PreToolUse hook matched on Bash that greps tool_input.command for patterns like rm -rf /, git push --force to a protected branch, or DROP TABLE, and exits 2 with an explanation if it matches. This is a hard stop that does not depend on the model remembering a rule buried in a long system prompt.

Auto-format after every edit

A PostToolUse hook matched on Edit|Write that runs your formatter (prettier, black, gofmt) against the file that was just touched. Claude never has to be told to format — it happens after every write, deterministically.

Log every tool call for an audit trail

A PostToolUse hook with no matcher that appends the tool name, input, and timestamp to a log file. Useful for compliance, debugging a long agentic session after the fact, or measuring which tools an agent actually uses in practice.

Desktop notification when Claude needs you

A Notification hook that pipes the payload into terminal-notifier (macOS) or a Slack webhook, so you stop tabbing back to a terminal every thirty seconds to check if a long-running session is still waiting on a permission prompt.

Security Considerations

Hook commands run with your full local shell permissions — the same access you have. Anthropic's own guidance is direct on this: treat hook scripts with the same scrutiny as production code, keep them in version control, and never write one that executes unsanitized input from the JSON payload. A blocking hook is a security boundary; a badly written one is a new attack surface. Review a hook script the way you'd review a CI pipeline step, because functionally that's what it is.

Best Practices

  • Commit project hooks to the repo. Team-wide rules belong in version control, not a local dotfile only you have.
  • Keep hooks fast. They run synchronously in the agent loop — a slow hook is a slow agent. Push heavy work to background jobs and just kick them off from the hook.
  • Scope matchers tightly. An unscoped hook on a hot event like PostToolUse runs on every single tool call. Match only the tools you actually need.
  • Fail loud, not silent. If a hook script errors, make that visible in stderr — a hook that fails quietly gives you false confidence that a guardrail is active when it isn't.
  • Prefer PreToolUse for hard constraints, prompts for soft preferences. If a rule must never be violated, put it in a hook with exit code 2. If it's a stylistic preference, a hook is overkill — that's what CLAUDE.md is for.

The Bottom Line

Hooks are how you convert a rule you keep repeating in a prompt into a rule that is actually enforced. Start with one: a PreToolUse guard on the one dangerous command you're most worried about, or a PostToolUse formatter you're tired of running by hand. Once it's working, the same pattern extends to logging, notifications, and session-start context loading — all without touching your system prompt.


Free: Claude custom instructions template pack

Eight copy-paste templates — developer, writer, analyst, CLAUDE.md starter, and more. Plus new guides in your inbox. No spam, unsubscribe anytime.

Or grab the templates directly — no email needed

Keep learning — for free

50+ AI courses. 590+ lessons. No paywall for starters.

Need help building this?

We build MCP servers, Claude workflows, and AI agents for teams. Strategy calls start at $150/hr.