CertSafari
    Anthropic Claude Certified Architect – Foundations (CCAR-F)· Lessons

    Domain 1 · Lesson 4/30

    Prerequisite Gates: Enforcing Workflow Order with Hooks

    Implement multi-step workflows with enforcement and handoff patterns

    12 min read
    3.86% of exam
    8 sources
    Published 4 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Explain why prompt instructions alone cannot guarantee tool-call ordering, and when deterministic enforcement is required
    • Design a PreToolUse/PostToolUse pair that blocks process_refund until get_customer has succeeded in the same session
    • Choose a matcher that gates a specific set of MCP tools without catching unrelated ones
    • Write a deny reason that lets Claude recover by completing the missing prerequisite
    • Decompose a multi-concern customer request into distinct items, investigate each in parallel with subagents that receive shared context through their prompts, and synthesize one resolution

    Key concept

    Programmatic prerequisite gate — A hook that runs between Claude's tool request and the tool's execution. It checks whether earlier workflow steps have completed and denies the call if they have not, so the ordering holds no matter what the model decided.

    1.Two places to put an ordering rule

    Take a refund workflow: verify the customer, check the order, then issue the refund. The order matters, and you can enforce it in one of two places. One is the prompt, for example a system prompt that says "always call get_customer before process_refund." The other is code that sits between Claude and the tool.

    To see why the choice matters, look at who decides what happens in the agent loop. On each turn Claude "evaluates the current state and determines how to proceed", which includes picking the next tool to call. A prompt instruction feeds into that choice. It makes the right order very likely, but the model still makes the call, so a prompt can influence the decision without guaranteeing it. The sources don't give a failure rate for prompt-only ordering. What the exam expects you to know is that the rate is not zero, and for some operations any non-zero rate is unacceptable.

    The loop also has a step the model doesn't control. After Claude requests a tool, the SDK runs it, and "You can use hooks to intercept, modify, or block tool calls before they run." Hooks are code you register. They fire on the tool call itself, whatever reasoning led to it. Claude Code's feature guide lists hooks as the tool to "Run deterministic logic on tool calls (audit, block, transform)", and its power-user tips put it this way: hooks let you deterministically run logic at points in the agent lifecycle.

    That gives you a decision rule. Use prompt guidance for preferences where an occasional slip costs little, such as tone, which lookup to try first, or how to word a summary. Use programmatic enforcement where compliance has to be deterministic. Identity verification before money moves is the standard example. The SDK hook docs list this kind of use directly: block dangerous operations before they execute, and "Require human approval for sensitive actions like database writes or API calls".

    Checkpoint 1 of 6· Check yourself

    A fintech support agent must never call process_refund until the customer's identity has been verified. Which design actually guarantees this ordering?

    Sources123

    2.Building the gate: record on success, enforce before execution

    A prerequisite gate has two parts, and each uses a different hook event.

    Record. Register a PostToolUse callback on get_customer. PostToolUse fires "After a tool call succeeds". If the lookup fails, PostToolUseFailure fires instead, so a failed lookup can never unlock the refund. The callback records that verification is done. Key that record by session, because "All hook inputs share session_id, cwd, and hook_event_name."

    Enforce. Register a PreToolUse callback on process_refund. It checks the record for the current session_id. If there's no record, it returns a deny decision. If there is one, it returns an empty object and the call goes through the normal flow.

    The docs' example below blocks writes to .env files rather than refunds. The deny shape is identical, though: put a different condition in the if statement and you have a refund gate.

    A PreToolUse callback that denies a tool call by returning permissionDecision: "deny", or allows it by returning an empty objectpython
    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 {}

    Watch out for one tempting shortcut. The callback also receives a tool_use_id, but that ID only "correlates PreToolUse and PostToolUse events for the same tool call." It ties the before and after of one call together. It does not link get_customer to a later process_refund, so it can't be your verification key.

    Next, decide which tools the gate covers. Tools that move money often come from an MCP server, and "MCP tools use the pattern mcp__<server>__<action>". The matcher string decides how a pattern is read:

    How a hook matcher string is evaluated
    Matcher valueEvaluated asExample from the docs
    "*", "", or omittedMatch allFires on every occurrence of the event
    Only letters, digits, _, -, spaces, , and |Exact string, or a list of exact stringsEdit|Write matches either tool exactly
    Contains any other characterJavaScript regular expression, unanchoredmcp__memory__.* matches every tool from the memory server

    So a single registration can cover a handful of money-moving tools: either an exact list joined with |, or a regular expression scoped to just those action names. Avoid a server-wide wildcard if the server also exposes read-only tools you don't want to gate. Above all, never leave the matcher empty on a gate. The troubleshooting guide warns: "Verify matcher patterns aren’t too broad: an empty matcher matches all tools".

    Checkpoint 2 of 6· Exam question

    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?

    Checkpoint 3 of 6· Fill the gap

    Which hook event key registers this callback so it can block a tool call before it runs?

    options = ClaudeAgentOptions(
        hooks={" ? ": [HookMatcher(matcher="Bash", hooks=[my_callback])]}
    )

    Sources43

    3.What happens when the gate fires

    Each time Claude requests a tool, the same pipeline runs: an event fires, the SDK collects the registered hooks, matchers filter which ones run, the callbacks execute, and each callback returns a decision. A deny is one of those decisions, but the workflow doesn't have to end there.

    For a command hook, permissionDecision: "deny" sends the reason back to Claude as the tool error. Claude reads it and can change course. The docs describe this for prompt-based hooks too. With continueOnBlock: true, they "return the reason to Claude as the tool error so it can adjust and continue, equivalent to a command hook’s permissionDecision: "deny"". Without that flag, a prompt-based PreToolUse deny ends the turn by default.

    That makes permissionDecisionReason part of how the workflow works, not just a log line. A reason like "Customer identity not verified: call get_customer first" tells Claude exactly which prerequisite is missing. Claude calls get_customer, your PostToolUse callback records success, and the retried process_refund passes. The gate enforces the order and its message steers the agent toward it. If a tool gets blocked when you didn't expect it, the SDK troubleshooting steps start with checking every PreToolUse hook for permissionDecision: 'deny' returns.

    Checkpoint 4 of 6· Put it in order

    Put the stages of hook resolution in the order the SDK runs them when Claude requests a tool

    1. 1.The SDK collects registered hooks
    2. 2.Callback functions execute
    3. 3.An event fires
    4. 4.Matchers filter which hooks run
    5. 5.Your callback returns a decision

    Checkpoint 5 of 6· Check yourself

    Your command hook denies process_refund with permissionDecisionReason "Customer identity not verified: call get_customer first". What does Claude experience?

    Sources43

    4.Multi-concern requests: decompose, investigate in parallel, synthesize

    Customers often raise several problems in one message: a refund for a damaged item, a delayed second order, and an address change. Handling them as one blob invites missed items. The pattern is to decompose the request into distinct items, investigate each one separately, then merge the findings into a single resolution. Anthropic's multi-agent guidance describes the same shape: the core implementation "decomposes a question into independent facets, runs subagents concurrently, then synthesizes the results."

    The lead agent does the decomposition and the synthesis. In between, each concern goes to its own subagent. The subagents docs give the payoff for the middle step: "multiple subagents can run concurrently, so independent subtasks finish in the time of the slowest one". Use this only when the items really are independent. If one concern depends on another's result, run them in sequence.

    Shared context needs care. A subagent does not see the lead's conversation: "each subagent runs in its own conversation, which starts fresh unless the subagent is a fork." The features guide states the practical consequence: what a subagent gets is "Whatever context the lead agent passes in the prompt". So when you fan out, put the shared facts into each subagent's prompt, such as the verified customer ID, the order numbers, and the specific concern that subagent owns. Without that, each subagent has to rediscover them or will guess.

    Finally, only each subagent's final message returns to the lead. The lead reconciles those summaries into one reply that addresses every item, and it states clearly any item that needs escalation. Hooks still apply along the way: hooks in settings.json "fire in the main agent and any subagents it spawns", so a prerequisite gate is not bypassed by delegating.

    Checkpoint 6 of 6· Check yourself

    A lead agent splits a customer message into three independent concerns and spawns one subagent per concern. The customer ID was verified earlier in the lead's conversation. How does each subagent learn it?

    Sources5678

    Exam traps

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

    1. 1.A forceful enough system prompt, with capitals, repetition and examples, makes identity verification before refunds effectively guaranteed.Why is that wrong?

      A prompt only shapes the model's choice of the next tool. Deterministic compliance needs code that intercepts the tool call itself, which is what hooks do.

      Covered in Two places to put an ordering rule

    2. 2.The tool_use_id passed to hooks can link the get_customer call to the later process_refund call, so it works as the verification key.Why is that wrong?

      tool_use_id only pairs the PreToolUse and PostToolUse events of one tool call. Track verification across calls by session_id, which every hook input carries.

      Covered in Building the gate: record on success, enforce before execution

    3. 3.Subagents spawned for each part of a multi-concern request automatically share the lead agent's conversation, so the lead need not repeat the customer details.Why is that wrong?

      Each subagent starts fresh. The lead must pass the shared facts in each subagent's prompt.

      Covered in Multi-concern requests: decompose, investigate in parallel, synthesize

    Sources

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

    1. 1.
      “You can use hooks to intercept, modify, or block tool calls before they run.”
      ↩︎ Two places to put an ordering rule
      “Claude evaluates the current state and determines how to proceed.”
      ↩︎ Two places to put an ordering rule
      “You can use hooks to intercept, modify, or block tool calls before they run.”
      ↩︎ Key concept
    2. 2.
      “Hooks let you deterministically run logic at points in the agent lifecycle.”
      ↩︎ Two places to put an ordering rule
    3. 3.
      “Require human approval for sensitive actions like database writes or API calls”
      ↩︎ Two places to put an ordering rule
      “All hook inputs share session_id, cwd, and hook_event_name.”
      ↩︎ Building the gate: record on success, enforce before execution
      “MCP tools use the pattern mcp__<server>__<action>”
      ↩︎ Building the gate: record on success, enforce before execution
      “Verify matcher patterns aren’t too broad: an empty matcher matches all tools”
      ↩︎ Building the gate: record on success, enforce before execution
      “Check all PreToolUse hooks for permissionDecision: 'deny' returns”
      ↩︎ What happens when the gate fires
      “correlates PreToolUse and PostToolUse events for the same tool call.”
      ↩︎ Exam trap 2
      “Matchers filter which hooks run”
      ↩︎ Checkpoint
    4. 4.
      “PostToolUse | After a tool call succeeds”
      ↩︎ Building the gate: record on success, enforce before execution
      “JavaScript regular expression, unanchored”
      ↩︎ Building the gate: record on success, enforce before execution
      “return the reason to Claude as the tool error so it can adjust and continue, equivalent to a command hook’s permissionDecision: "deny"”
      ↩︎ What happens when the gate fires
      “PreToolUse | Before a tool call executes. Can block it”
      ↩︎ Checkpoint
    5. 5.
      “decomposes a question into independent facets, runs subagents concurrently, then synthesizes the results.”
      ↩︎ Multi-concern requests: decompose, investigate in parallel, synthesize
    6. 6.
      “multiple subagents can run concurrently, so independent subtasks finish in the time of the slowest one”
      ↩︎ Multi-concern requests: decompose, investigate in parallel, synthesize
      “each subagent runs in its own conversation, which starts fresh unless the subagent is a fork”
      ↩︎ Multi-concern requests: decompose, investigate in parallel, synthesize
      “each subagent runs in its own conversation, which starts fresh unless the subagent is a fork”
      ↩︎ Exam trap 3
    7. 8.
      “These fire in the main agent and any subagents it spawns.”
      ↩︎ Multi-concern requests: decompose, investigate in parallel, synthesize
      “Run deterministic logic on tool calls (audit, block, transform)”
      ↩︎ Exam trap 1

    Continue to page 2 of 2

    Multi-Concern Requests and Structured Human Handoffs

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