GuideAgentic ways of working · Intermediate

Hooks: guardrails that run every time

Claude Code hooks run your scripts at fixed points in a session and can block, rewrite or add context. Every current event, matchers, the five handler types, exit codes and JSON output, precedence, debugging, security and seven recipes.

Checked 13 Oct 202619 min read7 hooks

On this page
  1. What it is
  2. When to use it
  3. How it works
  4. Members only: Set it up step by step
  5. Members only: Every hook event
  6. Members only: Matchers and the if filter
  7. Members only: Handler types and their fields
  8. Members only: Exit codes and JSON output
  9. Members only: Environment variables
  10. Members only: Recipes
  11. Members only: Debug a hook
  12. Members only: Security cautions
  13. Members only: Patterns that work
  14. Members only: Mistakes to avoid
  15. Members only: Exercises

A rule in CLAUDE.md is advice the model weighs. A hook is code that Claude Code runs every time a specific event happens, whether or not the model remembers the rule. Use hooks for the few things that must never depend on judgment: blocking edits to secrets, formatting every file, refusing to finish while tests fail.

What it is#

A hook is a handler you register in a settings file against a lifecycle event. When the event fires and the hook's matcher matches, Claude Code sends a JSON description of the event to your handler and acts on what comes back. A handler is one of five types:

  • command runs a shell command. The event JSON arrives on stdin, and the exit code and stdout carry the result. Most hooks are this type.
  • http sends the event JSON as a POST request to a URL and reads the response body.
  • mcp_tool calls a tool on an MCP server you already configured.
  • prompt asks a Claude model for a single-turn yes or no decision.
  • agent starts a subagent that can read files and run checks before it decides. Agent hooks are experimental.

Events come in three rhythms. SessionStart and SessionEnd fire once per session. UserPromptSubmit and Stop fire once per turn. PreToolUse and PostToolUse fire around every tool call inside the agentic loop. Around those sit events for permissions, subagents, compaction, notifications, file changes, worktrees and model switches, 33 in all as of Claude Code v2.1.296.

1. SessionStart: the session opens and its hooks run. 2. UserPromptSubmit: you send a prompt and its hooks can check it or add context. 3. The agent tries to run rm -rf ~/. The PreToolUse safety hook matches it and exits with code 2, so the call is blocked and the reason goes back to the agent. 4. The agent edits app.ts instead. The safety hook exits 0 and the call is allowed. 5. The tool runs. 6. PostToolUse: a formatter hook runs prettier on the edited file. 7. Stop: the agent finishes its turn and Stop hooks run.Safety hookchecks Bash commandsFormatter hookruns prettier --writerm -rf ~/exit 2 · blockedEdit app.tsexit 0 · allowedapp.tsformattedSessionStartUserPromptSubmitPreToolUseTool runsPostToolUseStoprm -rf ~/Edit app.tsExit code 2 blocks the call, and the reason goes back to the agent.

The key difference from instructions: a PreToolUse hook that denies a tool call blocks it in every permission mode, including bypassPermissions. A model can reason its way around a sentence in a prompt. It cannot get past a script that exits with code 2.

When to use it#

Reach for a hook when a rule must hold every time and a script can check it.

  • Block what must never happen: edits to .env files, force-pushes, DROP TABLE, pushes to main.
  • Fix what a machine does better than a model: run the formatter after every edit, sort imports, regenerate a file.
  • Gate the end of work: refuse to end a turn while tests fail.
  • Inform Claude of facts it cannot see: the current branch, the deploy freeze, last night's CI results.
  • Notify you when Claude is waiting, so you can work on something else.
  • Record what happened: one line per tool call in an audit log.

Something else fits better in three cases. For a plain allow or deny on a tool or path, write a permission rule first: rules are simpler, and Claude Code evaluates them whatever a hook returns. For guidance that needs judgment, such as "prefer small functions", use CLAUDE.md or a skill. For a check that needs reasoning over files, a prompt or agent hook works, but it is slower and less predictable than a script.

Other agents have hooks too, each in its own format. Cursor reads .cursor/hooks.json and ~/.cursor/hooks.json. GitHub Copilot reads .github/hooks/*.json in the cloud agent and the CLI, plus ~/.copilot/hooks/*.json in the CLI. Codex reads .codex/hooks.json, ~/.codex/hooks.json or a [hooks] table in config.toml, and asks you to trust each hook before it runs. Gemini CLI reads hooks from its settings.json files. Event names and payloads differ, such as Cursor's beforeShellExecution, Copilot's preToolUse and Gemini's BeforeTool, so port the script logic, not the JSON.

How it works#

Configuration has three levels: an event, a matcher group that filters it, and one or more handlers.

json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

When Claude decides to run rm -rf /tmp/build, five things happen in order.

  1. The PreToolUse event fires, and Claude Code prepares JSON with tool_name, tool_input, session_id, cwd and other fields.
  2. The matcher Bash matches the tool name, so the group activates.
  3. The optional if filter, written in permission rule syntax, checks the arguments. rm -rf /tmp/build matches Bash(rm *), so the handler spawns. For npm test it would not, and no process would start.
  4. The script reads the JSON on stdin and decides. Exit code 2 with a message on stderr blocks the call. Exit 0 with no output leaves the decision to the normal permission flow.
  5. Claude Code blocks the call and shows Claude the reason, and Claude tries another approach.

All matching handlers run in parallel. For PreToolUse decisions the most restrictive answer wins, in the order deny, defer, ask, allow.

Set it up step by step

1. Choose where the hook lives Location Scope Shared --- --- --- ~/.claude/settings.json All your projects No, your machine only .claude/settings.json One project Yes, commit it .claude/settings.local.json One project No, kept out of git Managed policy settings Whole organization Yes, set by admins A plugin's ...

Members only below this line

Get the rest of this guide free

What you get on this page:

  • 7 hooks

Free. You also get Stratiflux Weekly: one email a week, unsubscribe anytime.

Also in the members part

  1. Every hook event
  2. Matchers and the if filter
  3. Handler types and their fields
  4. Exit codes and JSON output
  5. Environment variables
  6. Recipes
  7. Debug a hook
  8. Security cautions
  9. Patterns that work
  10. Mistakes to avoid
  11. Exercises

Sources

Checked against these on 13 Oct 2026.

  1. Hooks reference (Claude Code docs) (opens in a new tab)code.claude.com
  2. Automate actions with hooks (Claude Code docs) (opens in a new tab)code.claude.com
  3. Settings files and precedence (Claude Code docs) (opens in a new tab)code.claude.com
  4. Configure permissions: extend permissions with hooks (opens in a new tab)code.claude.com
  5. Claude Code changelog (opens in a new tab)code.claude.com
  6. Cursor hooks (opens in a new tab)cursor.com
  7. About hooks for GitHub Copilot (opens in a new tab)docs.github.com
  8. Codex hooks (opens in a new tab)developers.openai.com
  9. Gemini CLI hooks (opens in a new tab)geminicli.com