CertSafari
    CLAUDE-CERTIFIED-ARCHITECT-FOUNDATIONS-CCAR-F · Lessons

    Domain 1 · Lesson 4/30

    Programmatic Enforcement vs Prompt Guidance: Prerequisite Gates with Hooks

    Implement multi-step workflows with enforcement and handoff patterns

    8 min read
    3.86% of exam
    5 sources
    Published 28 Sep 2026
    Docs as of 27 Sep 2026

    What you will be able to do

    • Explain why a prompt instruction cannot guarantee workflow ordering, and when that matters
    • Design a PreToolUse prerequisite gate that blocks process_refund until get_customer has returned a verified customer ID
    • Write one hook matcher that covers a family of sensitive tools without duplicating hook code

    Key concept

    Programmatic enforcement — Code outside the model, such as a hook, runs on a lifecycle event and can allow or deny a tool call no matter what the model decided. Prompt guidance only asks the model to behave, so the outcome depends on how the model interprets it.

    1.Two ways to control ordering: asking and enforcing

    A customer-support agent has an ordering rule: verify the customer's identity, then touch money. You can express that rule in two ways. The first is prompt-based guidance: you write it into the system prompt, for example "Always call get_customer before process_refund." The second is programmatic enforcement: code that runs outside the model checks each tool call and refuses the ones that break the rule.

    Prompt guidance works, and it works most of the time. The tool-use documentation says the model's tool-calling behaviour "is steerable through your system prompt" and that stronger phrasing pushes it further. The same page is candid about the limit: a model may fill in values you never supplied, and "This behavior is not guaranteed, especially for more ambiguous prompts and for less capable models." So an instruction lowers the failure rate. It does not bring it to zero.

    Hooks against instruction-based guidance, as the Claude Code features overview compares them
    AspectHookSkill (instructions Claude follows)
    RunsA shell command, HTTP request, MCP tool call, LLM prompt, or subagentInstructions Claude reads and follows
    Triggered byLifecycle events such as PostToolUse or SessionStartYou typing /<name>, or Claude matching the description to your task
    DeterminismAlways fires on its event; the trigger is guaranteedClaude interprets the instructions; outcome can vary
    Best forLinting after edits, blocking unsafe commands, logging, notificationsWorkflows that need reasoning, reference material, multi-step tasks

    The deciding question is what a single failure costs. If the agent occasionally forgets to cite a knowledge-base article, a prompt is enough. If it occasionally refunds money to someone whose identity it never checked, one failure in a thousand is still a compliance incident. When compliance must be deterministic, such as identity verification before a financial operation, the rule belongs in code. The prompt can still explain the rule so the model usually follows it on the first try. The code makes sure breaking it has no effect.

    There is also a middle option. The tool-use overview notes: "To require a tool call rather than rely on prompting, set tool_choice." That forces the model to call a tool on a given turn. It does not express a condition like "only after a verified customer exists," which depends on what earlier calls returned. A gate that can inspect each call is the tool for that.

    Sources12

    2.Building a prerequisite gate with PreToolUse

    Hooks are the enforcement mechanism in both Claude Code and the Agent SDK. The Agent SDK hooks page lists what they are for, starting with: "Block dangerous operations before they execute, like destructive shell commands or unauthorized file access". The event that makes blocking possible is PreToolUse, which the hooks reference describes as "Before a tool call executes. Can block it". It fires on every tool call inside the agentic loop, together with PostToolUse.

    The flow is fixed: an event fires, the SDK collects registered hooks, matchers filter which hooks run, callbacks execute, and "Your callback returns a decision". In the SDK example below, the callback reads the tool's input and returns a deny decision with a reason. Returning an empty object allows the call.

    An Agent SDK PreToolUse callback that denies a tool call based on its inputpython
    async def protect_env_files(input_data, tool_use_id, context):
        # Extract the file path from the tool's input arguments
        file_path = input_data["tool_input"].get("file_path", "")
        file_name = file_path.split("/")[-1]
    
        # Block the operation if targeting a .env file
        if file_name == ".env":
            return {
                "hookSpecificOutput": {
                    "hookEventName": input_data["hook_event_name"],
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "Cannot modify .env files",
                }
            }
    
        # Return empty object to allow the operation
        return {}

    A prerequisite gate uses the same shape but checks state as well as input. The pattern has two parts:

    1. Record the prerequisite. A PostToolUse hook, which fires after a tool call succeeds, sees get_customer's result. If the result contains a verified customer ID, it stores that ID in your application's session state. 2. Check it downstream. A PreToolUse hook on process_refund looks up that state. If no verified ID exists, or the refund targets a different customer, it returns deny with a reason such as "Customer identity not verified; call get_customer first."

    The reason matters. The model reads it and can correct course, in the same way that a tool_result marked is_error lets Claude "incorporate this error into its response to the user." A gate with a clear reason blocks the unsafe call and also points the agent at the missing step. The ordering is then guaranteed by code, and the prompt only makes it efficient.

    Sources345

    3.One gate for a family of tools: matchers

    Real systems rarely have a single sensitive tool. Refunds, credits and voided authorizations all move money, and all need the same identity check. Writing one hook per tool name duplicates code, and it quietly fails open the day someone adds a fourth money-moving tool and forgets the hook.

    Matchers solve this. Registration pairs a matcher pattern with a list of callbacks, and the SDK example matches two tools with one pattern, commenting that "The matcher filters to only Write and Edit tool calls":

    Registering one callback for two tools with a single alternation matcherpython
        options = ClaudeAgentOptions(
            hooks={
                # Register the hook for PreToolUse events
                # The matcher filters to only Write and Edit tool calls
                "PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])]
            }
        )

    The Claude Code settings example uses the same form with "matcher": "Bash|PowerShell". For a billing server, list exactly the money-moving tools, joined with |. This covers the whole family in one registration and leaves read-only lookups from the same server unmatched, so they run without the gate.

    Two more properties make hooks suitable for compliance. First, "Hooks merge: all registered hooks fire for their matching events regardless of source." A project-level gate can't be switched off by a user-level hook that doesn't mention it. Second, a hook that makes no decision falls through: the Claude Code example exits 0 with the comment "no decision; normal permission flow applies". So a gate only has to decide the cases it owns.

    A refund workflow requires that process_refund always be called with the exact customer_id captured by an earlier verified get_customer call, never a value the model retypes from the conversation. A PreToolUse hook already blocks the call when no verified ID exists in session state. What should the hook do once a verified ID is present, to prevent the model from substituting a different ID string?

    Sources324

    Exam traps

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

    1. 1.A firm enough system-prompt instruction ("ALWAYS verify identity before refunding") makes the ordering deterministic.Why is that wrong?

      Prompts steer the model's tool use, but the docs state the behaviour is not guaranteed. Deterministic ordering needs a hook that can block the call.

      Covered in Two ways to control ordering: asking and enforcing

    2. 2.Gating several sensitive tools requires one PreToolUse hook registration per tool name.Why is that wrong?

      A matcher can name several tools with alternation, so one registration and one callback cover the whole family. Unlisted read-only tools stay ungated.

      Covered in One gate for a family of tools: matchers

    Sources

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

    1. 1.
      “This behavior is not guaranteed, especially for more ambiguous prompts and for less capable models.”
      ↩︎ Two ways to control ordering: asking and enforcing
      “To require a tool call rather than rely on prompting, set tool_choice.”
      ↩︎ Two ways to control ordering: asking and enforcing
      “This behavior is not guaranteed, especially for more ambiguous prompts and for less capable models.”
      ↩︎ Exam trap 1
    2. 2.
      “Claude interprets the instructions; outcome can vary”
      ↩︎ Two ways to control ordering: asking and enforcing
      “Hooks merge: all registered hooks fire for their matching events regardless of source.”
      ↩︎ One gate for a family of tools: matchers
      “Always fires on its event; the trigger is guaranteed”
      ↩︎ Key concept
    3. 3.
      “Block dangerous operations before they execute, like destructive shell commands or unauthorized file access”
      ↩︎ Building a prerequisite gate with PreToolUse
      “The matcher filters to only Write and Edit tool calls”
      ↩︎ One gate for a family of tools: matchers
      “The matcher filters to only Write and Edit tool calls”
      ↩︎ Exam trap 2
    4. 4.
      “Before a tool call executes. Can block it”
      ↩︎ Building a prerequisite gate with PreToolUse
      “no decision; normal permission flow applies”
      ↩︎ One gate for a family of tools: matchers
    5. 5.
      “Claude will then incorporate this error into its response to the user.”
      ↩︎ Building a prerequisite gate with PreToolUse

    Continue to page 2 of 2

    Multi-Concern Requests: Parallel Investigation, Synthesis and Human Handoff