AnthropicClaude Certified Architect – Professional2026-105.1 Implement guardrails and safety controls
Require approval and fail closed before risky calls
Completed by the knowledge check
Learning objective
Implement pre-execution gates that block or require approval for high-risk actions and fail closed when a check is unavailable
Core idea
A blocking PreToolUse decision prevents the proposed tool call. Operating that control adds another requirement: an unavailable checker or reviewer must never become implicit approval.
Design three outcomes explicitly: prohibited, eligible for approval, and authorized to proceed. A check failure belongs to none of them. Record it as unavailable and keep the risky operation unexecuted. The service can continue useful investigation or return a deferred-service response without granting the capability that the missing check protects.
The choice is architectural because the approval service, runtime, and downstream handler have different failure modes. The architect must specify what remains stopped, who can approve, and how the resumed request is checked again.
How it works
Separate mandatory policy from interactive permission
Keep the mandatory business predicate in PreToolUse or the application-owned execution path. The Agent SDK evaluates hooks before permissions. Its TypeScript canUseTool callback, called can_use_tool in Python, handles requests that reach the runtime’s user-input step. It is not invoked for every tool call: an earlier approval can resolve a call before that callback.
When a request passes mandatory policy but needs a person, a PreToolUse result with hookSpecificOutput.permissionDecision: "ask" sends it through confirmation. In the SDK, provide the corresponding user-input callback and a real review interface. The TypeScript callback returns an approval or denial using behavior: "allow" or behavior: "deny"; the user-input guide documents the complete response shapes.
Treat the callback’s input as the proposed operation to review. Display the actual target and consequential arguments, not a model-written reassurance. If the reviewer declines, is unavailable, or the application cannot establish a valid decision, return a denial. Do not auto-approve merely to keep an unattended run moving.
Permission confirmation still needs the application’s business meaning. For an illustrative deployment tool, approval of “use this tool” is weaker than approval of a particular release in a particular environment. Have the execution check consult the application’s approval record for that release and environment. The record format and expiration are application policy, not SDK defaults.
Choose a supported wait path
A callback can await the review interface and then return its decision. Bound that wait by the service’s deadline, and handle cancellation. The TypeScript user-input callback receives an AbortSignal; the Python context’s signal field is reserved for future use, so do not assume identical cancellation APIs.
The PreToolUse hook decides before the callback runs, and a reviewer who declines or is unavailable produces a denial.
For work that must outlive the active request, Claude Code also documents permissionDecision: "defer". In non-interactive -p mode, including Agent SDK execution, it ends the turn with stop_reason: "tool_deferred" and preserves the pending call. Resuming invokes PreToolUse again. Deferral preserves a proposal; it does not approve it.
That mechanism has a critical constraint: it works only for a single tool call in the turn. If Claude proposes several calls, "defer" is ignored with a warning and normal permissions continue. Therefore, a mandatory approval design cannot depend on deferral alone. Retain a denying policy predicate whenever the required approval is absent, including the multi-call path.
Revalidate when work resumes. If the release, target, permissions, or approval record no longer match, the resumed call stays denied. A person authorizing one operation must not accidentally authorize a different operation generated later.
Make checker failures blocking
Claude Code command hooks and Agent SDK callback hooks have different timeout semantics. Do not copy the assumption from one into the other:
| Enforcement surface | Documented failure behavior | Fail-closed design |
|---|---|---|
SDK PreToolUse callback | A timeout cancels the callback, discards its output, and leaves the tool unexecuted | Set the matcher’s timeout in seconds; handle lookup failures with an explicit denial |
Claude Code command hook | Non-blocking failures normally let the action continue | Set onFailure: "block"; requires Claude Code v2.1.295 or later |
Claude Code http hook | Failures normally continue; a timeout does not itself block PreToolUse | Set onFailure: "block"; requires Claude Code v2.1.295 or later |
See 3.2.5 for command-hook exit codes and explicit denial signaling; the cross-family comparison here determines how an unavailable approval check stays closed.
Do not return {} from an exception handler as a convenient default. That provides no denial. Return an explicit rejection for an unavailable mandatory check and distinguish its reason from a policy breach. Keep enforcement synchronous: a background hook output cannot block the operation it has already allowed to continue.
Key distinctions and trade-offs
Denial versus pending review: A rejected action is stopped. A review record is an application artifact with its own lifecycle. A denial reason mentioning review does not create that record or notify anyone by itself.
Fail closed versus fail silent: Both can stop execution, but only an observable unavailable outcome lets operators distinguish an outage from a policy change. Count unavailable checks separately and assign the checker service to an on-call owner.
Availability versus authority: Closing a deployment capability during an approval outage reduces service availability. Keeping read-only diagnosis available can preserve usefulness, provided those calls satisfy their own authorization. The fallback must not expose an alternate execution tool that bypasses the missing check.
Architecture / decision scenario
A technology company lets an agent investigate failed releases and request production deployment. Its delivery target permits a five-minute wait for approval, but production deployment requires the release manager’s approval of the exact build and environment. Read-only diagnosis may continue during a review outage.
The team proposes placing every approval in canUseTool, with auto-allow rules to reduce prompts for ordinary deployments. The architect rejects the conflict: an earlier allow can prevent that callback from running. Mandatory approval validation belongs in PreToolUse, regardless of the permission path.
The hook denies prohibited releases and unavailable policy checks. For an eligible release awaiting approval, it returns "ask"; the callback records the release manager’s decision for the exact build and environment before allowing execution. The deployment handler also rejects execution without that matching approval record. Work exceeding the five-minute service deadline remains unexecuted. A durable review can complete separately, followed by a fresh checked attempt.
On the Agent SDK surface, acceptance tests withhold reviewer approval, interrupt the approval lookup, and exceed the callback timeout; each must show zero deployment-handler entries. The same team also operates a separate Claude Code CLI deployment with a command hook and onFailure: "block". Its tests remove the script, corrupt its output, and time out the lookup, again requiring zero handler entries. This design uses a fresh checked attempt after durable review, so it does not depend on deferral. The release manager owns approval policy; platform operations owns both surfaces and their checker availability.
Common exam traps
- Using
canUseToolas the mandatory predicate for every call. - Treating a reviewer timeout as approval.
- Expecting an
httphook timeout to blockPreToolUsewithoutonFailure: "block". - Relying on
"defer"for a multi-call turn. - Reusing approval after the target or arguments change.
- Logging a checker failure and returning a non-denying result.
What to remember
Put mandatory checks before permissions, and provide an explicit supported path for approval. Fail closed when a check cannot produce a decision. Test the failure defaults of the exact hook family and keep resumed operations subject to fresh validation.
Knowledge check
A TypeScript Agent SDK deployment agent must never run mcp__release__deploy for a build that lacks the change board’s approval record. The agent uses default permission mode, no ask or deny rule matches the call, and the MCP server does not require user interaction. To reduce prompts, the team adds that tool to allowedTools and puts the approval-record check in the canUseTool callback. What is wrong with this design?
canUseTool is consulted last, only for calls no earlier step resolved. A call an allow rule auto-approves never reaches the callback, so the approval check is silently bypassed. A mandatory predicate belongs in PreToolUse or the tool’s own execution path, which run whatever the permission path.A team runs Claude Code v2.1.295 or later in CI with a PreToolUse http hook that consults an approval service before production commands; the CI’s permission rules already allow those commands. During an approval-service outage, the hook’s request times out. No other field is set on the hook. What happens to the command, and what should change?
onFailure: "block" on the hooktimeoutonFailure: "continue" on the hookcommand and http hooks, a timeout is a non-blocking failure: the call continues through the normal permission flow. Setting onFailure: "block" , available from v2.1.295, makes that failure block the command. A longer timeout only delays the same outcome during an outage. The expectation that a timeout blocks comes from Agent SDK callback hooks, where a PreToolUse timeout does block the call.A custom review interface runs claude -p with a PreToolUse hook that returns permissionDecision: "defer" for production writes, so a release manager can approve them later. In testing, Claude sometimes proposes a production write together with two reads in the same turn. What should the architect conclude?
"defer" works only when Claude makes a single tool call in the turn; with several calls it is ignored with a warning and the write proceeds through the normal permission flow. A mandatory approval therefore needs a policy check that denies whenever the approval record is absent. Even when deferral applies, it preserves the proposal rather than approving it: resuming runs PreToolUse again.An Agent SDK PreToolUse callback looks up a payment-approval record before issue_refund runs. When the lookup service throws, the handler logs the error and returns {}. What does that return do, and what should replace it?
References
- Anthropic, Configure permissions
- Anthropic, Handle approvals and user input
- Anthropic, Intercept and control agent behavior with hooks
- Anthropic, Hooks reference