Claude 在两种情况下需要用户输入:需要工具使用权限(如删除文件)和需要澄清问题(通过 AskUserQuestion 工具)。通过 canUseTool 回调处理这两种情况,回调会暂停执行直到返回决策。
核心机制
canUseTool 回调:接收工具名、输入参数和上下文,返回 allow 或 deny 决策。回调可无限期挂起等待用户响应。
两种触发场景:
- 工具审批:
tool_name为"Bash"、"Write"等,需要用户决定是否允许 - 澄清问题:
tool_name == "AskUserQuestion",Claude 生成问题和选项,需要用户选择
回调返回值:
- Python:
PermissionResultAllow(updated_input=input_data)或PermissionResultDeny(message="原因") - TypeScript:
{ behavior: "allow", updatedInput: input }或{ behavior: "deny", message: "原因" }
五种响应模式:
- 批准(原样执行)
- 批准并修改输入(如将路径重定向到沙箱)
- 批准并记住(写入 settings.local.json,下次跳过提示)
- 拒绝(Claude 看到原因并可调整策略)
- 建议替代方案(拒绝并引导 Claude 换方向)
关键细节
处理澄清问题:
async def can_use_tool(tool_name, input_data, context):
if tool_name == "AskUserQuestion":
answers = {}
for q in input_data.get("questions", []):
print(f"\n{q['header']}: {q['question']}")
for i, opt in enumerate(q["options"]):
print(f" {i+1}. {opt['label']} - {opt['description']}")
response = input("Your choice: ").strip()
answers[q["question"]] = response
return PermissionResultAllow(
updated_input={"questions": input_data["questions"], "answers": answers}
)
return PermissionResultAllow(updated_input=input_data)Python 特殊要求:can_use_tool 需要流式输入模式,且需要一个 dummy PreToolUse Hook 保持流连接:
async def dummy_hook(input_data, tool_use_id, context):
return {"continue_": True}
options = ClaudeAgentOptions(
can_use_tool=can_use_tool,
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
)AskUserQuestion 格式:每次调用支持 1-4 个问题,每个问题 2-4 个选项,支持单选和多选。
TypeScript 预览功能:设置 toolConfig.askUserQuestion.previewFormat: "html" 可让 Claude 为选项生成 HTML 预览。
与其他概念的关联
- permissions - 权限评估流程(canUseTool 是最后一步)
- hooks - 自动化审批场景用 Hooks(不需要用户交互)
- streaming-vs-single-mode - canUseTool 需要流式输入模式
实践要点
- 自动化场景(无需用户交互)应使用 Hooks 而非 canUseTool 回调
- 回调可无限期等待,如进程可能超时,返回
defer决策让进程退出并从持久化会话恢复 - 如果
tools数组中未包含AskUserQuestion,Claude 无法提问 - 子 Agent 目前不支持
AskUserQuestion - 修改工具输入时,Claude 不会被告知输入已被修改