CertSafari
    CLAUDE-CERTIFIED-DEVELOPER-FOUNDATIONS-CCDV-F · Lessons

    Domain 7 · Lesson 21/25

    Claude Hooks: Deterministic Guardrails for Tool Calls

    Claude Hooks

    9 min read
    2.03% of exam
    4 sources
    Published 29 Sep 2026
    Docs as of 27 Sep 2026

    What you will be able to do

    • Explain why a hook enforces a safety rule where a prompt instruction only requests it
    • Tell apart the hook events that can prevent an action from the ones that only observe it
    • Trace how an event passes through matcher, if condition and handler, and name the five handler types
    • Choose the settings location for a guardrail that must be shared or enforced organization-wide

    Key concept

    Hook as a deterministic guardrail — A hook is your own code that Claude Code runs at a fixed point in the agent lifecycle. Because it fires every time its event matches, a PreToolUse hook can refuse a destructive tool call no matter what the model decided to do.

    1.Why a guardrail belongs in a hook, not in the prompt

    Suppose an agent can run shell commands and edit files, and you never want it to run rm -rf or overwrite a .env file. You could write that rule into the system prompt or CLAUDE.md. Claude would usually follow it. But an instruction in context is something the model weighs against everything else it is reading, and 'usually' is not a safety control.

    A hook moves the rule out of the model and into code that the harness runs. Anthropic's tips describe hooks as a way to deterministically run logic at points in the agent lifecycle. The Agent SDK documentation puts the safety use first in its list of what hooks are for: they can block dangerous operations before they run, like destructive shell commands or unauthorized file access. The same list also covers auditing every tool call, sanitizing inputs and outputs, and requiring human approval for sensitive actions like database writes.

    The split to remember: the prompt shapes what Claude *tries* to do, and the hook decides what is actually *allowed* to happen. A hook doesn't need the model to agree with it.

    Sources12

    2.Which events can stop an action, and which only see it

    Claude Code emits many hook events, but not all of them can serve as a guardrail. What matters is when an event fires compared with the action it describes. PreToolUse and PostToolUse both fire on every tool call in the agentic loop (EndConversation calls skip both). Only PreToolUse fires *before* execution, and the hooks reference says plainly that it can block the call. PostToolUse fires after a tool call succeeds. By then the file is already written or the command has already run, so a PostToolUse hook suits formatting, logging or audit work, not prevention.

    A handful of other events are also documented with a blocking or modifying role. The table below keeps to that wording.

    Hook events by what they can do to the action they describe (Claude Code hooks reference and Agent SDK hooks table)
    EventWhen it firesGuardrail role
    PreToolUseBefore a tool call executesCan block it
    UserPromptExpansionWhen a user-typed command expands into a prompt, before it reaches ClaudeCan block the expansion
    PreModelSwitchBefore Claude Code applies a requested model switchCan block the switch
    ElicitationResultAfter a user responds to an MCP elicitation, before the response is sent back to the serverModify or block the response
    PermissionRequestWhen a tool call needs a permission decisionCustom permission handling
    PostToolUseAfter a tool call succeedsObserve only: audit, format, log
    PostToolUseFailureAfter a tool call failsObserve only: handle or log errors
    PostToolBatchAfter a full batch of parallel tool calls resolvesObserve only: runs before the next model call

    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?

    Sources32

    3.How a hook resolves: event, matcher, if condition, handler

    Configuring a hook takes three choices: an event to respond to (such as PreToolUse), a matcher group that filters when it fires (such as 'only for the Bash tool'), and one or more handlers to run on a match. At runtime Claude Code works through the same chain in order. The event fires with a JSON payload such as { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" } }. The matcher checks the tool name. An optional if condition narrows the match further using permission-rule syntax. The handler runs, and Claude Code acts on what it returns.

    In the reference example below, the matcher limits the hook to Bash, and "if": "Bash(rm *)" means the script only starts for rm commands, not for every shell call.

    A PreToolUse hook scoped by matcher and if condition, taken from the Claude Code hooks referencejson
    {
      "hooks": {
        "PreToolUse": [
          {
            "matcher": "Bash",
            "hooks": [
              {
                "type": "command",
                "if": "Bash(rm *)",
                "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
                "args": []
              }
            ]
          }
        ]
      }
    }

    The type field picks the kind of handler. There are five documented types, and they differ in where the guardrail logic actually runs.

    The five hook handler types
    typeWhat the handler does
    commandExecutes shell commands or scripts
    httpSends the event JSON as a POST request to a URL
    mcp_toolCalls a tool on a configured MCP server
    promptEvaluates a prompt with an LLM, using the $ARGUMENTS placeholder for context
    agentRuns an agentic verifier with tools for complex verification tasks

    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?

    Sources34

    4.Where a guardrail is configured decides who it protects

    A hook only protects the sessions that load it, so where you put it is part of the design. The hooks reference lists these locations:

    Hook configuration locations, scope and shareability
    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

    A guardrail the whole team relies on belongs in .claude/settings.json, committed to the repo. If the organization has to enforce it, managed policy settings are the place. Managed policy can also lock the setup down: the reference describes a managed configuration in which user, project, local and plugin hooks are all blocked, apart from plugins that managed settings force-enable. HTTP handlers can be restricted as well. When allowedHttpHookUrls is defined, Claude Code runs an HTTP hook only if its URL matches the merged allowlist.

    Sources3

    Exam traps

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

    1. 1.A PostToolUse hook that detects rm -rf in the command can stop the deletion.Why is that wrong?

      PostToolUse fires only after the tool call has succeeded, so the deletion has already happened. Prevention needs PreToolUse, which fires before the call executes and can block it.

      Covered in Which events can stop an action, and which only see it

    2. 2.Putting a guardrail hook in .claude/settings.local.json shares it with everyone who works on the project.Why is that wrong?

      The local settings file is project-scoped but not shareable. It is gitignored when Claude Code saves a setting to it. For a team-wide hook, commit it in .claude/settings.json, or use managed policy settings to enforce it across the organization.

      Covered in Where a guardrail is configured decides who it protects

    Sources

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

    1. 1.
      “Hooks let you deterministically run logic at points in the agent lifecycle.”
      ↩︎ Why a guardrail belongs in a hook, not in the prompt
      “Hooks let you deterministically run logic at points in the agent lifecycle.”
      ↩︎ Key concept
    2. 2.
      “Block dangerous operations before they execute, like destructive shell commands or unauthorized file access”
      ↩︎ Why a guardrail belongs in a hook, not in the prompt
      “Require human approval for sensitive actions like database writes or API calls”
      ↩︎ Why a guardrail belongs in a hook, not in the prompt
      “Modify or block the response before it returns to the server”
      ↩︎ Which events can stop an action, and which only see it
    3. 3.
      “PreToolUse | Before a tool call executes. Can block it”
      ↩︎ Which events can stop an action, and which only see it
      “on every tool call inside the agentic loop: PreToolUse and PostToolUse, except EndConversation calls, which skip both”
      ↩︎ Which events can stop an action, and which only see it
      “Define one or more hook handlers to run when matched”
      ↩︎ How a hook resolves: event, matcher, if condition, handler
      “Your user, project, local, and plugin hooks are blocked.”
      ↩︎ Where a guardrail is configured decides who it protects
      “Claude Code runs an HTTP hook handler only if its URL matches the merged allowlist”
      ↩︎ Where a guardrail is configured decides who it protects
      “PostToolUse | After a tool call succeeds”
      ↩︎ Exam trap 1
      “No, gitignored when Claude Code saves a setting to it”
      ↩︎ Exam trap 2
    4. 4.
      “agent: run an agentic verifier with tools for complex verification tasks”
      ↩︎ How a hook resolves: event, matcher, if condition, handler
      “prompt: evaluate a prompt with an LLM (uses $ARGUMENTS placeholder for context)”
      ↩︎ How a hook resolves: event, matcher, if condition, handler

    Continue to page 2 of 2

    Blocking Destructive Actions with PreToolUse Hooks