CertSafari
    Anthropic Claude Certified Developer – Foundations (CCDV-F)· Lessons

    Domain 7 · Lesson 21/25

    Claude Code hook events, matchers and handler types

    Claude Hooks

    12 min read
    2.03% of exam
    3 sources
    Published 12 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Name the hook events that fire around each tool call, each turn and each session
    • Tell which hook events can stop an action and which only observe it after the fact
    • Write matchers that use exact names or regular expressions to target specific tools
    • Choose a handler type and a settings location for a guardrail hook

    Key concept

    Hook as a deterministic control point — A hook is a handler that you register against a lifecycle event. Claude Code runs it every time that event fires, whatever the model intended. A hook on an event that fires before an action takes effect can refuse that action, so a written policy becomes a check that always runs.

    1.Hooks as the layer that runs on every tool call

    A prompt instruction such as "never run rm -rf" is something Claude weighs. A hook is something Claude Code executes. You attach a handler to a lifecycle event, and the handler runs each time that event fires, whatever the model reasoned its way to. Of all the defences described in [Guardrail layering](lesson:7.2), this one does not depend on the model following instructions: it runs on every tool call, including calls the model was talked into by injected content.

    Hook events fire at three different rates. SessionStart and SessionEnd fire once per session. UserPromptSubmit, Stop and StopFailure fire once per turn. PreToolUse and PostToolUse fire around every tool call inside the agentic loop, with one exception: EndConversation calls skip both. Guardrails care most about the tool-call pair, because tool calls are where an agent changes files, runs shell commands and calls external services.

    The same mechanism covers more than blocking. One hook can log every Bash command to an audit file. Another can make a person approve database writes. A third can rewrite a tool's input before it runs. Whichever you build, the handler is your own code, sitting at a fixed point in the loop.

    Checkpoint 1 of 7· Check yourself

    A team registers a PreToolUse hook with matcher * so that it audits every tool call. Which call will the hook never see?

    Sources12

    2.Which hook events can stop an action

    Whether a hook can block comes down to timing. A hook can stop an action only if its event fires before that action takes effect. PreToolUse fires before the tool executes, so it can cancel the call. UserPromptSubmit fires before Claude processes the prompt, so blocking it means the prompt never reaches Claude. UserPromptExpansion can stop a typed command from expanding into a prompt, and PreModelSwitch can veto a requested model switch. PostToolUse and PostToolUseFailure fire after the tool has already succeeded or failed. Use them to audit, lint or report back to Claude. They are too late to prevent anything.

    What exit code 2 does on common events
    Hook eventCan block?What happens on exit 2
    PreToolUseYesBlocks the tool call
    UserPromptSubmitYesBlocks the prompt, so it never reaches Claude
    UserPromptExpansionYesBlocks the expansion
    StopYesPrevents Claude from stopping, continues the conversation
    ConfigChangeYesBlocks the configuration change from taking effect (except policy_settings)
    PostToolUseNoShows stderr to Claude; the tool already ran
    PermissionRequestNoExit code 2 isn’t honored; deny through the decision object instead

    "Block" does not always mean "refuse a dangerous action". On Stop and SubagentStop, blocking keeps Claude working. You would use that to stop a turn ending before the tests pass, not to protect the system. PermissionRequest catches people out the other way round. It fires exactly when a tool call needs approval, yet exit code 2 does nothing there, so a hook that wants to refuse must answer through the decision object in its JSON output.

    Checkpoint 2 of 7· Check yourself

    An auditor lists hook events that can prevent the associated action. Which entry is wrong?

    Checkpoint 3 of 7· Exam question

    A platform team wants to prevent Claude Code from ever running `rm -rf` against production paths, regardless of what the model decides to do. Which hook event should they use to guarantee the command never executes?

    Sources1

    3.Aiming a hook with matchers and if conditions

    Hook configuration has three levels. You choose an event, add a matcher group that filters when it fires, and list one or more handlers to run when it matches. For tool events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied), the matcher is compared with the tool name. Other events filter on other fields: SessionStart on how the session started, PreCompact on whether compaction was manual or auto. UserPromptSubmit, Stop, PostToolBatch and a few others have no matcher support and fire on every occurrence.

    How a matcher string is read depends on the characters in it. *, an empty string or no matcher at all matches everything. A string made only of letters, digits, _, -, spaces, commas and | is an exact name or a list of exact names, so Edit|Write matches those two tools and nothing else. Any other character turns the whole string into an unanchored JavaScript regular expression. ^Notebook therefore matches every tool whose name starts with Notebook, and mcp__memory__.* matches every tool on the memory MCP server. MCP tools are named mcp__<server>__<action>, which is why a regex is the usual way to cover a whole server.

    A matcher can only see the tool name. To narrow a hook further, add an if condition on the handler, written in permission-rule syntax such as Bash(rm *). The handler then starts only for calls whose arguments fit. A script that runs on every Bash call has to parse and dismiss most of them. With if, it runs only for the commands it exists to judge.

    A PreToolUse hook aimed at Bash, narrowed by an if condition to rm commandsjson
    {
      "hooks": {
        "PreToolUse": [
          {
            "matcher": "Bash",
            "hooks": [
              {
                "type": "command",
                "if": "Bash(rm *)",
                "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
                "args": []
              }
            ]
          }
        ]
      }
    }

    Checkpoint 4 of 7· Put it in order

    Put the stages of resolving this hook for a rm -rf /tmp/build call in order

    1. 1.Claude Code acts on the result the handler returned
    2. 2.The matcher checks the tool name against Bash
    3. 3.The hook handler runs
    4. 4.The if condition checks the call against Bash(rm *)
    5. 5.The PreToolUse event fires with the tool name and input

    Checkpoint 5 of 7· Match them up

    Match each matcher value to what it fires on

    Tap a term, then the definition that fits it.

    Sources1

    4.Handler types and where hook configuration lives

    The handler's type field decides what kind of code makes the decision. A guardrail that pattern-matches a command string fits naturally in a command handler, which is a script that reads the event JSON and replies through its exit code or stdout. The other types send the event to a service you run, to an MCP tool, or to a model.

    The five handler types
    typeWhat the handler does
    commandRuns a local command or script that receives the event data
    httpPOSTs the event data to a URL
    mcp_toolCalls a tool on a configured MCP server
    promptSingle-turn LLM evaluation
    agentMulti-turn verification with tool access; experimental and may change

    For a hard rule, prefer a command handler. It returns the same decision for the same input. Prompt and agent handlers put a model's judgment inside the check, which helps when a policy is too fuzzy for a regex. HTTP handlers can be restricted by an administrator: once allowedHttpHookUrls is set at any settings level, only hook URLs on the merged allowlist run.

    Where you write the hook decides who it protects. A guardrail in ~/.claude/settings.json covers all of your own projects but no one else. Put it in .claude/settings.json and it is committed with the repository, so everyone who clones the repo gets it. .claude/settings.local.json is gitignored and stays personal. Managed policy settings apply across the organization and are controlled by administrators. Hooks can also come with a plugin or be declared in a skill's or subagent's frontmatter, scoped to when that component is active.

    Hook configuration locations
    LocationScopeShareable
    ~/.claude/settings.jsonAll your projectsNo, local to your machine
    .claude/settings.jsonSingle projectYes, can be committed to the repo
    .claude/settings.local.jsonSingle projectNo, gitignored when Claude Code saves a setting to it
    Managed policy settingsOrganization-wideYes, admin-controlled
    Plugin hooks/hooks.jsonWhen plugin is enabledYes, bundled with the plugin
    Subagent frontmatterWhile that subagent is runningYes, defined in the subagent file

    Checkpoint 6 of 7· Check yourself

    A tech lead wants a hook that blocks rm -rf to protect every developer who clones one repository, with no per-machine setup. Where should it go?

    Checkpoint 7 of 7· Exam question

    A security engineer writes a command-hook script for PreToolUse that inspects `tool_input.command` and, upon detecting a dangerous pattern, prints an explanation to stderr and exits without emitting any JSON. Which exit code causes Claude Code to block the tool call using that stderr message?

    Sources31

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.A PostToolUse hook that exits 2 can stop a destructive tool call.Why is that wrong?

      PostToolUse fires after the tool has already succeeded. Exit 2 only sends stderr back to Claude. Prevention has to happen in PreToolUse.

      Covered in Which hook events can stop an action

    2. 2.Exiting 2 from a PermissionRequest hook denies the permission request.Why is that wrong?

      PermissionRequest ignores exit code 2. To refuse, the hook has to return a decision in its JSON output.

      Covered in Which hook events can stop an action

    3. 3.Edit|Write is a regular expression, so it also matches tools whose names merely contain Edit or Write.Why is that wrong?

      A matcher made only of letters, digits, _, -, spaces, commas and | is a list of exact names. Only other characters switch it to a regular expression.

      Covered in Aiming a hook with matchers and if conditions

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “on every tool call inside the agentic loop: PreToolUse and PostToolUse, except EndConversation calls, which skip both”
      ↩︎ Hooks as the layer that runs on every tool call
      “per turn: UserPromptSubmit, Stop, and StopFailure”
      ↩︎ Hooks as the layer that runs on every tool call
      “Blocks the prompt, so it never reaches Claude.”
      ↩︎ Which hook events can stop an action
      “Prevents Claude from stopping, continues the conversation”
      ↩︎ Which hook events can stop an action
      “Before Claude Code applies a model switch that you or a client requested. Can block the switch”
      ↩︎ Which hook events can stop an action
      “mcp__memory__.* matches every tool from the memory server”
      ↩︎ Aiming a hook with matchers and if conditions
      “always fires on every occurrence”
      ↩︎ Aiming a hook with matchers and if conditions
      “when defined at any settings level, Claude Code runs an HTTP hook handler only if its URL matches the merged allowlist”
      ↩︎ Handler types and where hook configuration lives
      “Shows stderr to Claude; the tool already ran”
      ↩︎ Exam trap 1
      “Exit code 2 isn’t honored for this event and the permission flow proceeds unchanged. Deny through the decision object instead”
      ↩︎ Exam trap 2
      “Edit|Write and Edit, Write each match either tool exactly”
      ↩︎ Exam trap 3
      “Shows stderr to Claude; the tool already ran”
      ↩︎ Prediction
      “Add a matcher group to filter when it fires”
      ↩︎ Checkpoint
      “JavaScript regular expression, unanchored”
      ↩︎ Checkpoint
      “Yes, can be committed to the repo”
      ↩︎ Checkpoint
    2. 2.
      “Require human approval for sensitive actions like database writes or API calls”
      ↩︎ Hooks as the layer that runs on every tool call
      “Block dangerous operations before they execute, like destructive shell commands or unauthorized file access”
      ↩︎ Key concept
    3. 3.
      “multi-turn verification with tool access. Agent hooks are experimental and may change.”
      ↩︎ Handler types and where hook configuration lives

    Continue to page 2 of 2

    PreToolUse guardrails: exit codes and permission decisions

    Spotted a mistake, or was something unclear? Tell us.