Claude 在两种情况下需要用户输入:需要工具使用权限(如删除文件)和需要澄清问题(通过 AskUserQuestion 工具)。通过 canUseTool 回调处理这两种情况,回调会暂停执行直到返回决策。

核心机制

canUseTool 回调:接收工具名、输入参数和上下文,返回 allow 或 deny 决策。回调可无限期挂起等待用户响应。

两种触发场景:

  1. 工具审批:tool_name 为 "Bash"、"Write" 等,需要用户决定是否允许
  2. 澄清问题: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 预览。

与其他概念的关联

实践要点

  • 自动化场景(无需用户交互)应使用 Hooks 而非 canUseTool 回调
  • 回调可无限期等待,如进程可能超时,返回 defer 决策让进程退出并从持久化会话恢复
  • 如果 tools 数组中未包含 AskUserQuestion,Claude 无法提问
  • 子 Agent 目前不支持 AskUserQuestion
  • 修改工具输入时,Claude 不会被告知输入已被修改