A prompt is a request. A hook is a law.
A rule in a system prompt is a request. The model reads it together with everything else in the conversation, and usually it complies. A rule that must hold every single time belongs in code, at the moment your code holds the tool call and nothing has run yet. In Claude Code and the Claude Agent SDK that moment is a PreToolUse hook. The first episode of my video series on agent architecture shows it on a single refund. This post goes through it with the code from the film.
Episode 1 of Agentic Whiteboard, in English with English captions. Watch it on YouTube
Meet Blob, an AI support agent. At the top of its system prompt sits one rule: NEVER refund more than $500. A customer asks for $2,000, and Blob answers: “Sure! Refunding $2,000 now.” Blob did not break and did not go rogue. It did what a language model does with a prompt.
The episode runs a little over seven minutes. It opens Agentic Whiteboard, my new YouTube channel about agent architecture. Three characters carry the story: Blob, the agent; Ada, the support lead who runs it; and Kai, the architect who built it.
Why a rule in the prompt slips
Kai starts where almost everyone starts. He writes the rule into the system prompt and tests it. Ten requests, then a hundred, and Blob refuses every time. So he ships it.
The model does not execute a system prompt the way a computer executes code. It reads the prompt together with the rest of its context, and then it decides. Usually the rule wins. But the rule is one line. Around it, in the film, there is a customer whose order was a gift for their daughter and arrived broken. The conversation is thirty-seven turns long, and the request is phrased in a way no test tried. The details are invented, but every agent in production has its own.
The exam guide for the Claude Certified Architect Foundations says it directly, in task statement 1.4:
“When deterministic compliance is required (e.g., identity verification before financial operations), prompt instructions alone have a non-zero failure rate”
The usual next move is capital letters. NEVER, MANDATORY, the same rule three times. Maybe the failures get rarer. They do not go to zero. Anthropic's prompting guide adds a warning of its own: prompts that used aggressive wording such as “CRITICAL: You MUST use this tool when…” may make newer models overtrigger. Its fix is to soften the language. A bigger sign is still a sign.
Where the gate goes
Claude never runs the tools you define. When it wants one, it stops with stop_reason: "tool_use" and names the tool and the inputs it chose: refund_customer, order A-1042, amount 2000. Your code runs the tool and sends the result back. I described the whole loop, from the first tool_use block to end_turn in a separate post.
That loop has a gap. The model has decided, your code holds the request, and the money has not moved yet. The gate goes into that gap. It is code that runs before the tool runs. It does not read the conversation. It does not care about the gift or how polite the request was. It reads one thing: the amount. 2000 is more than 500, so the call is denied. Not usually. Every time.
The PreToolUse hook in the Agent SDK
In Claude Code and the Claude Agent SDK this gate has a name: a PreToolUse hook. It fires after Claude has chosen a tool and its inputs, and before the tool runs. It can let the call through, or deny it and give a reason. Claude sees that reason. Here is Kai's hook in Python, exactly as it appears in the film:
async def refund_limit(input_data, tool_use_id, context):
amount = input_data["tool_input"]["amount"]
if amount > 500:
return {"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason":
"Over $500. Escalate to a human."}}
return {}
HookMatcher(matcher="mcp__support__refund_customer",
hooks=[refund_limit], on_failure="block")
The hook itself is nine lines. The HookMatcher call below it decides whether it really holds, and it carries two details.
- The matcher names one tool. A custom tool in the Agent SDK runs on an in-process MCP server, so its full name is
mcp__{server_name}__{tool_name}. Without a matcher the hook fires on every tool. Onlookup_orderthe line that reads["amount"]raises an error, because that tool has no amount. on_failure="block". By default, when a hook callback throws, the call goes on to the normal permission flow. The guard fails open, and it fails exactly on the case nobody tested. With this option a crashing guard blocks the call. So let the error propagate: a callback that catches its own error and returns{}is not failing. If the SDK cannot honour the option, the docs say the query fails at startup and sends no prompt.
On the Messages API, with your own loop, you do not need a hook system. You already own the gap. Check the input before you run the tool. If it breaks the rule, do not run it. Send back a tool result marked as an error, with the reason in plain words:
tool_result · is_error: true
"Over $500. Escalate to a human."
Different machinery, same principle: the rule lives in code, at the one point every refund has to pass through. Whether you write that loop yourself or take it from the SDK is a separate choice. I compared what the bare API, the Agent SDK and the CLI each leave to your own code before.
Anthropic admits to its exams only people from Claude Partner Network organizations. Join our collective of AI consultants and sit all four exams through our partner organization.
Exit code 1 alone does not block
Claude Code can also run a hook as a shell command, and that has one gotcha. To block, the command exits with code 2, or it returns JSON with a deny decision. Exit code 1 on its own does not block. By default it is a non-blocking error, and the call goes ahead.
exit 2 → blocked ✓
permissionDecision: "deny" → blocked ✓
exit 1 alone → call goes ahead ✗
The hooks reference says it plainly: “If your hook is meant to enforce a policy, use exit 2.” A hook can also be set to block when it fails (onFailure: "block"), and then other exit codes block too. Without that setting, a guard that exits 1 passes the demo and fails in production.
The JSON route has its own trap. For PreToolUse the field is hookSpecificOutput.permissionDecision. The top-level decision field is deprecated for this event. The docs also warn that when a hook exits 0 and prints JSON that fails schema validation, the action goes ahead.
After the deny
So the refund is blocked. A gate that only slams shut is half a design, because Blob is still talking to an upset customer. This is where the reason matters. Kai's hook does not just say no. It says: “Over $500. Escalate to a human.” Kai also gave Blob one more tool, escalate_to_human. Blob calls it with a short summary:
escalate_to_human({
"order_id": "A-1042",
"summary": "Broken gift,
$2,000 requested, over the limit."
})
The case lands with Ada, who can actually approve it. The customer hears the truth: “I can't approve this myself, so I've passed it to a teammate who can.”
Notice what is guaranteed and what is not. The block is guaranteed. What happens next is still the model's choice. So make the good choice easy: a clear reason and the right tool. Not a generic error, not a fake success, not a dead conversation. The Architect Foundations guide explains why a generic error such as “Operation failed” keeps the agent from choosing the right recovery (task statement 2.2), and it calls a silent success and ending the whole workflow anti-patterns (5.3). For the handoff it names four fields a human needs when they cannot see the transcript: customer ID, root cause, refund amount and recommended action.
The $500 limit is not my invention either. Task statement 1.5 of the same guide uses it as its own example: hooks that block refunds exceeding $500 and redirect to human escalation.
The Agentic Architect
Practical patterns, case studies, agentic projects taken apart and AI news. Zero spam, once a week.
You'll get one email to confirm. Unsubscribe with one click. Privacy policy
Prompt or code: one question
Should every rule become a hook? The film sorts five rules with one question: must it hold every single time?
Tone and format can slip once and nobody loses money, so they stay in the prompt. The other three must hold. Identity verification is a prerequisite gate: the refund tool refuses to run until verification has succeeded. The production table needs a hook on the tool that runs the SQL, or no write access to production at all, with that tool denied outright.
One trap in Claude Code: putting a tool on the allowed list only means it runs without asking. It does not take the other tools away. If something must never run, deny the tool. A bare tool name in a deny rule removes the tool from Claude's context entirely. A scoped rule matches the command as it is written, so it cannot reliably catch a DROP hidden inside SQL.
The same split opens my twelve design rules for agents, written while I was preparing for the exam. Everything that must always hold goes into code, and the prompt keeps what needs judgment.
When a hook is overkill
Do not turn every sentence of your prompt into code. A hook costs something: code to write, tests to keep green, edge cases to think through. If the model keeps picking the wrong tool, the cheapest fix is usually a clearer tool description. Anthropic's tool documentation calls detailed descriptions “by far the most important factor in tool performance”. If the output format drifts, show an example. Start with the cheapest fix that reaches the root cause.
Kai's test fits in two sentences. Imagine the rule slips once. If that is a bug, write a better prompt; if it is a disaster, write a hook.
If you are studying for the Claude Certified Architect Foundations exam, keep one thing from this episode. When a question says a rule must always hold, strengthening the system prompt is the trap. Enforce the rule in code.
Where this comes from. The technical details come from Anthropic's documentation for Claude Code, the Agent SDK and the Claude API, and from the Claude Certified Architect Foundations exam guide, version 1.0 of July 2026. A second agent checked the film's narration and on-screen code against those sources on 6 November 2026, and the code above is the checked version. Blob, Ada, Kai and the refund story are invented. Agentic Whiteboard and the Certified AI Consultant Program are independent, not affiliated with or endorsed by Anthropic.
SOURCES
- A prompt is a request. A hook is a law. Agentic Whiteboard, episode 1 (on YouTube as “Why Your AI Agent Ignores Your System Prompt (Hooks Explained with Animations)”)
- Claude Code: hooks reference
- Claude Agent SDK: hooks
- Claude Agent SDK: custom tools
- Claude Code: permissions
- Claude API: how tool use works
- Claude API: handle tool calls
- Claude API: define tools
- Claude API: prompting best practices
- Claude Certified Architect Foundations exam guide, version 1.0 (PDF)