What you will be able to do
- Decide which agent actions need a human checkpoint and explain why irreversible actions come first
- Wire a canUseTool approval callback in the Claude Agent SDK and read the tool name and input it receives
- Choose among the reviewer responses (approve, approve with changes, approve and remember, reject, suggest an alternative, redirect)
- Route Claude's clarifying questions to a human through AskUserQuestion
Key concept
Human approval checkpoint — A point where the agent's proposed action stops and waits for a person to allow it, change it or refuse it before it runs. In the Agent SDK this is the canUseTool callback, and it runs only for calls that no earlier rule or mode has already settled.
1.Where a human checkpoint earns its cost
Human-in-the-loop validation means an agent stops before certain actions and waits for a person to approve, change or refuse them. The first design question is where to put those stops. Anthropic's guidance for agents that act on the web answers it directly: put them in front of irreversible actions. Its examples are submitting forms, making purchases, sending messages and modifying data. It also describes this checkpoint as the most effective mitigation against prompt injection, whatever the classifiers catch. A classifier can miss an injected instruction. A person who sees the concrete action before it runs gets a final chance to notice that it is not what they asked for.
The same guidance adds two companions to the checkpoint. The first is to scope the agent's permissions, because a tool the agent does not have needs no approval and cannot be abused. The second is to log every action the agent takes, so reviewers can audit afterwards what they did not see live. Use all three together: a human gate on high-stakes actions, a narrow toolset, and a complete action log.
Yes. Anthropic calls classifiers one layer of defense, not a complete solution, and names human confirmation before irreversible actions such as sending messages as the single most effective mitigation against prompt injection, regardless of classifier performance.
In the Claude Agent SDK the checkpoint has a fixed place in how each tool call is resolved. The SDK checks hooks first, then deny rules, then ask rules, then the permission mode, then allow rules. Only a call that none of those settles reaches the approval callback. So human review is the last step in the chain, and everything before it decides how many calls a person actually sees.
2.The canUseTool approval callback
You pass a function as canUseTool (TypeScript) or can_use_tool (Python). It fires in two situations. The first is when Claude wants a tool that no permission rule or mode auto-approves. The second is when Claude calls the AskUserQuestion tool to ask something. The callback gets enough information to show a reviewer exactly what is about to happen:
| Argument | What the reviewer learns from it |
|---|---|
| toolName | Which tool Claude wants, for example Bash, Write or Edit |
| input | The parameters Claude is passing, which differ per tool (Bash: command, description, timeout; Write: file_path, content) |
| options (TS) / context (Python) | Optional suggestions (proposed PermissionUpdate entries to avoid re-prompting) and a cancellation signal |
The smallest useful callback asks the person and turns the answer into one of two results: allow with the input to run, or deny with a message.
canUseTool: async (toolName, input) => {
console.log(`Claude wants to use ${toolName}`);
const approved = await askUser("Allow this action?");
if (approved) {
return { behavior: "allow", updatedInput: input };
}
return { behavior: "deny", message: "User declined" };
};For a Bash call, the documented example shows the reviewer the command and its description before asking. A prompt that shows only the tool name gives the reviewer nothing to judge.
A fintech company's Claude-powered agent can initiate wire transfers through a custom MCP tool. Even though the operations team has configured allowedTools to auto-approve most read and reporting tools, they want every wire-transfer tool call to require a human's explicit approval no matter what permission mode the session is later switched to, including bypassPermissions. Which approach best satisfies this requirement?
Correct answer: A — Register a PreToolUse hook scoped to the wire-transfer tool that returns permissionDecision "ask" for every call, since hooks execute before deny rules, ask rules, the permission mode check, and allow rules, and their decision holds even under bypassPermissions.
- A. Correct. Hooks run first in the evaluation order and a PreToolUse hook's decision applies regardless of permission mode, including bypassPermissions, guaranteeing the prompt for this tool.
- B. Incorrect mechanism. A bare allow-rule entry auto-approves every matching call and the call never reaches canUseTool, so this design silently removes the intended approval step.
- C. Incorrect. acceptEdits only auto-approves file edits and specific filesystem commands; other tools still fall through to normal permission handling, which could still resolve via an existing allow rule rather than always prompting.
- D. Incorrect. Deny rules are evaluated before canUseTool in the flow, so the callback has no mechanism to reverse a deny decision for a specific call.
Sources3
3.More than yes or no: shaping the reviewer's answer
Allow and deny are the only two return types, but they cover six reviewer decisions. Allowing with a modified updatedInput lets a person fix a call instead of rejecting it. Denying with a descriptive message tells Claude why it was refused and what to try next.
| Decision | What happens |
|---|---|
| Approve | The tool executes as Claude requested |
| Approve with changes | The input is modified before execution, for example to sanitize paths or add constraints |
| Approve and remember | A suggested permission rule is echoed back so matching calls skip the prompt next time |
| Reject | The tool is blocked and Claude is told why |
| Suggest alternative | The tool is blocked, and the message steers Claude toward what the user wants instead |
| Redirect entirely | Streaming input sends Claude a completely new instruction |
# Get user approval
response = input("Allow this action? (y/n): ")
# Return allow or deny based on user's response
if response.lower() == "y":
# Allow: tool executes with the original (or modified) input
return PermissionResultAllow(updated_input=input_data)
else:
# Deny: tool doesn't execute, Claude sees the message
return PermissionResultDeny(message="User denied this action")Sources3
4.Humans answering, not only approving
A human in the loop can also supply information. When Claude is unsure what the user wants, it can call AskUserQuestion. That call goes to the same callback, so your handler should check whether tool_name equals AskUserQuestion and show a question instead of an approval prompt. This matters if you restrict the agent's tools: when you pass an explicit tools array, AskUserQuestion has to be in it or Claude cannot ask.
The Python SDK has one extra requirement. Its documented example streams the prompt and registers a no-op PreToolUse hook, labelled a required workaround, because that hook keeps the stream open so can_use_tool can run.
Sources3
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Denying a tool call just stops it, and Claude gets no feedback about why.Why is that wrong?
A deny result carries a message that Claude reads. Rejecting a call and suggesting an alternative both work through that message.
Covered in More than yes or no: shaping the reviewer's answer
2.Clarifying questions always reach the callback, even when you pass a restricted tools array.Why is that wrong?
With an explicit tools array, AskUserQuestion must be listed, or Claude cannot ask the user anything through the callback.
Covered in Humans answering, not only approving
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Have the agent pause and request user confirmation before performing irreversible actions such as submitting forms, making purchases, sending messages, or modifying data.”
↩︎ Where a human checkpoint earns its cost“This is the single most effective mitigation against prompt injection regardless of classifier performance.”
↩︎ Where a human checkpoint earns its cost“Classifiers are one layer of defense, not a complete solution.”
↩︎ Where a human checkpoint earns its cost - 2.
“canUseTool callback: prompt users for approval at runtime, when no earlier step resolves the call.”
↩︎ Where a human checkpoint earns its cost“canUseTool callback: prompt users for approval at runtime, when no earlier step resolves the call.”
↩︎ Key concept - 3.https://code.claude.com/docs/en/agent-sdk/user-inputOfficial docs
“The parameters Claude is passing to the tool. Contents vary by tool.”
↩︎ The canUseTool approval callback“Additional context including optional suggestions (proposed PermissionUpdate entries to avoid re-prompting) and a cancellation signal.”
↩︎ The canUseTool approval callback“Approve with changes: modify the input before execution (for example, sanitize paths, add constraints)”
↩︎ More than yes or no: shaping the reviewer's answer“Approve and remember: echo a suggested permission rule back so matching calls skip the prompt next time”
↩︎ More than yes or no: shaping the reviewer's answer“If you specify a tools array, include AskUserQuestion for this to work.”
↩︎ Humans answering, not only approving“# Required workaround: dummy hook keeps the stream open for can_use_tool”
↩︎ Humans answering, not only approving“Reject: block the tool and tell Claude why”
↩︎ Exam trap 1“If you specify a tools array, include AskUserQuestion for this to work.”
↩︎ Exam trap 2