1.5 · Lesson 5 of 7
Agent SDK Hooks
What you need to know
Agent SDK hooks inject deterministic behaviour into an otherwise probabilistic system. They sit at the boundary between the model's decisions and the real world, intercepting tool calls and results to enforce business rules and normalise data. Hooks are how you implement the programmatic side of the enforcement spectrum from lesson 1.4.
Two types of hooks
PostToolUse runs after a tool executes and before the model processes the result. It intercepts the tool result and transforms it. The model receives clean, normalised data regardless of which tool produced it.
PreToolUse runs before a tool executes. It intercepts the outgoing tool call and can block it, modify it, or redirect it to another workflow. If the hook blocks the call, the tool never runs.
What each hook returns
A PreToolUse hook answers with a permissionDecision of allow, deny, ask, or defer, plus an optional permissionDecisionReason. An optional updatedInput rewrites the tool's arguments before it runs. The arguments arrive as tool_input on a PreToolUseHookInput.
A PostToolUse hook reads tool_response on a PostToolUseHookInput and can set updatedToolOutput to replace what the model sees, for built-in and MCP tools alike. The older updatedMCPToolOutput covered MCP tools only and is deprecated. Neither field changes one fact: by the time PostToolUse fires, the tool has already run, so blocking there stops the loop but does not undo the side effect. (Agent SDK hooks guide, checked September 2026.)
// PreToolUse
{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Why the call is denied",
updatedInput: { /* optional rewritten arguments */ }
}
}
// PostToolUse
{
hookSpecificOutput: {
hookEventName: "PostToolUse",
updatedToolOutput: { /* replacement the model sees */ }
}
}Register each callback under a matcher. MCP tool names follow mcp__<server>__<action>. Return {} to leave the call unchanged.
PostToolUse hooks: data normalisation
Different MCP tools return data in different formats. A customer database might return Unix timestamps. An order system might return ISO 8601 dates. One status API returns numeric codes while another returns strings. Without normalisation, the model interprets those mixed formats on every iteration, and it can parse a timestamp correctly once and misread it the next time.
- Unix timestamps → ISO 8601 dates
- Numeric status codes → human-readable strings
- Currency values → a consistent decimal format with a currency code
- Date strings in regional formats → one standard format
The model then receives the same shape every time, whichever tool or backend produced the payload.
PreToolUse hooks: policy enforcement
PreToolUse hooks are the implementation mechanism for the prerequisite gates in lesson 1.4. They intercept the outgoing call and apply the business rule before anything runs.
- Refund threshold. Intercept
process_refund. If the amount exceeds $500, block the call and redirect to a human escalation workflow. The refund tool never executes. - AML prerequisite. Intercept
transfer_funds. If the anti-money laundering check has not passed for this session, block the call and tell the agent to completeaml_checkfirst. - Manager approval. Intercept
approve_discountfor discounts above 20%. Pause execution and route the request to a manager approval queue. The tool runs only after that approval.
The decision framework
If one failure would lose money or create legal risk, use a hook. If the rule is a formatting preference or a style guideline, prompt guidance is enough. The exam uses a prompt-based answer as the distractor whenever the scenario needs deterministic enforcement.
| Requirement | Mechanism | Characteristic |
|---|---|---|
| Must be followed 100% of the time | Hooks | Deterministic |
| Preferred, but occasional deviation is acceptable | Prompts | Probabilistic |
Hooks vs prompts
International transfers must pass AML checks. A prompt such as "Always complete AML verification before processing international transfers" works about 95% of the time. The other 5% skip the check, which is a regulatory violation. A PreToolUse hook blocks transfer_funds until aml_check returns a pass. That is 100%. No transfer executes without the check. The prompt does not provide 100% enforcement.
Responses should be formatted in markdown. A prompt that asks for headers and bullet points works most of the time. An occasional plain-text reply is not a business risk. A hook is unnecessary overhead. A formatting preference does not need deterministic enforcement.
Refunds above $500 require human approval. A prompt that says to escalate works most of the time. One failure is a large refund with no approval. A PreToolUse hook intercepts process_refund, checks the amount, and blocks anything above $500 so it routes to a person. That holds on every run.
Beyond PreToolUse and PostToolUse
Practical example: data format chaos
A customer support agent uses three MCP tools:
get_customerreturns dates as Unix timestamps and status as numeric codes.lookup_orderreturns dates as ISO 8601 strings and status as English strings.check_shippingreturns dates as DD/MM/YYYY and status as single-character codes (Sfor shipped,Pfor pending).
Without a PostToolUse hook, the model interprets three date formats and three status representations on every iteration. Sometimes it converts a Unix timestamp. Sometimes it swaps the day and month in DD/MM/YYYY. Sometimes it reads P as processed instead of pending.
With a PostToolUse hook, every result is normalised before the model sees it:
- All dates → ISO 8601 (
2024-03-15T12:00:00Z) - All status codes → human-readable strings (
shipped,pending,delivered)
P means pending, not processed. The model always receives that English string, so the misreading disappears.
Exam traps
Using PostToolUse hooks to block policy-violating actions
PostToolUse runs after tool execution. By the time the hook fires, the non-compliant action has already been processed. Use PreToolUse hooks, before execution, to block actions before they happen.
Enhanced prompt instructions as the solution for 100% compliance requirements
Prompts provide probabilistic compliance. If the business requires 100% enforcement — financial operations, regulatory compliance, security checks — only hooks provide deterministic guarantees. A stronger prompt does not reach 100%.
Suggesting model-side data transformation instead of PostToolUse hooks for normalisation
Relying on the model to normalise heterogeneous data formats introduces inconsistency. PostToolUse hooks ensure clean, consistent data reaches the model every time, regardless of which tool produced it.
Confusing the direction of hooks — PostToolUse runs after execution, PreToolUse runs before
PostToolUse transforms results after a tool runs. PreToolUse blocks or modifies calls before a tool runs. Using the wrong hook direction means either missing the opportunity to prevent an action or unnecessarily blocking completed work.
Practice scenario
An agent occasionally processes international transfers without required compliance checks. The compliance team requires 100% enforcement of anti-money laundering (AML) checks before any international transfer is executed. The current system uses prompt instructions that work approximately 95% of the time. What is the correct approach?
Build exercise
Agent SDK Hooks for Normalisation and Policy Enforcement
60 minutes
What you will learn
- The distinction between PostToolUse (after execution, data normalisation) and PreToolUse (before execution, policy enforcement)
- Why hooks provide deterministic guarantees that prompts cannot match
- How to normalise heterogeneous data formats from multiple MCP tools into one schema
- How to implement threshold-based and prerequisite-based policy enforcement with pre-execution hooks
- The decision framework: hooks for 100% requirements, prompts for preferences
Step 1
Create three MCP tools with different formats
Create an agent with three MCP tools. Tool A returns Unix timestamps and numeric status codes. Tool B returns ISO 8601 dates and string statuses. Tool C returns DD/MM/YYYY dates and single-character status codes.
Why: This recreates the data format chaos example. Without normalisation, the model must interpret three date formats and three status representations, and parsing drifts from one iteration to the next.
You should see: Three tool implementations that each return data with distinct date and status formats. Tool A uses epoch seconds and numeric codes, Tool B uses ISO strings and English statuses, Tool C uses DD/MM/YYYY and single characters.
Step 2
Normalise results in a PostToolUse hook
Implement a PostToolUse hook that intercepts tool results and normalises dates to ISO 8601 and status codes to human-readable English strings.
Why: PostToolUse runs after execution but before the model processes the result. That is the correct direction for data normalisation. The exam tests that PostToolUse transforms data after execution, not before.
You should see: A HookCallback registered under PostToolUse that reads tool_response and rewrites it: Unix timestamps and DD/MM/YYYY dates to ISO 8601, numeric and single-character status codes to English strings. The rewritten object comes back as updatedToolOutput inside hookSpecificOutput.
Step 3
Verify the model receives one schema
Test with queries that require results from all three tools, and confirm the model sees consistent dates and statuses.
Why: Consistent data removes interpretation errors. Without normalisation the model can swap day and month in DD/MM/YYYY, or read status P as processed instead of pending.
You should see: Three tool results that all use ISO 8601 dates and English status strings, whichever tool produced them. The model response should reference dates and statuses consistently. Record the normalised object inside the normalising hook.
Step 4
Block refunds over $500 before they run
Add a PreToolUse hook that blocks process_refund when the amount exceeds $500 and redirects to a human escalation workflow.
Why: A PreToolUse hook runs before execution, so the refund never processes. PostToolUse is the wrong direction for blocking, because by then the action has already occurred.
You should see: A PreToolUse callback that inspects the refund amount and denies anything above $500, returning permissionDecision deny with a permissionDecisionReason. The refund tool never executes for denied calls.
Step 5
Require a passing AML check before transfer_funds
Add a PreToolUse hook that blocks transfer_funds until aml_check has returned a pass result in the current session.
Why: Prompt instructions reach about 95%. A regulatory rule needs 100%. The hook is deterministic enforcement that no prompt matches.
You should see: Two callbacks: a PreToolUse hook on transfer_funds that denies until session state records a passing AML check, and a PostToolUse hook on aml_check that sets that state.
Step 6
Prove blocked calls never execute
Attempt both blocked operations and confirm the hooks prevent them before execution. Then meet the prerequisites and run the same operations.
Why: The check is that the blocked tool never executes. A log line after the fact is not enforcement.
You should see: Both denied operations leave their tool handlers untouched, and the model receives the permissionDecisionReason. Once the prerequisites are met, the same operations run.
Sources
- Claude Agent SDK overview — Anthropic
- Claude Agent SDK hooks — Anthropic, source for permissionDecision and updatedToolOutput
- Claude Code hooks reference — PreCompact — Anthropic
- Building with the Claude API — Anthropic, Skilljar