工具事件
PreToolUse(含各工具的 tool_input 字段、allow/deny/ask/defer 决策)、PermissionRequest、PostToolUse、PostToolUseFailure、PostToolBatch、PermissionDenied。
PreToolUse
在 Claude 生成工具参数之后、处理工具调用之前运行。匹配除 EndConversation 之外的任意工具名:内置工具(Bash、PowerShell、Edit、Write、Read、Glob、Grep、Agent、Workflow、WebFetch、WebSearch、AskUserQuestion、ExitPlanMode)和任何 MCP 工具名。想在某个文件在磁盘上变化时运行(不论谁写的),用 FileChanged,而不是按文件编辑工具名匹配——PreToolUse 在变化之前运行,也只在 Claude 调用工具时触发;你在提示里用 @ 引用的文件是不经工具调用直接插入内容的,也不触发它。Agent SDK 的回调 Hook 在 PreToolUse 超时会阻塞该工具调用,Claude 收到点名超时的错误结果;另一个 Hook 返回的明确 deny 仍然优先。
输入:tool_name、tool_input、tool_use_id;MCP 工具还带 mcp_server 对象(含服务器 name 和说明定义来源的 source,如 plugin、sdk 及配置来源)。对 Write、Edit、Read,tool_input.file_path 始终是绝对路径:Claude Code 在 Hook 运行前展开 ~ 和相对路径,所以按路径匹配的 Hook 不能靠 ~ 或相对写法绕过。Windows 上路径用反斜杠,即使 Hook 在 Git Bash 下运行;用正斜杠写的比较(如检查 /src/)永远匹配不上,工具调用会被当作没东西可阻止而放行。比较前先规范化分隔符(Bash 里 FILE_PATH="${FILE_PATH//\\//}",Python 里 file_path.replace("\\", "/")),并匹配 /src/ 这样的路径段而不是用 ^ 锚定。tool_input 字段因工具而异:
| 工具 | tool_input 字段 |
|---|---|
Bash | command、可选 description、timeout(毫秒,超过上限会被截到上限而不是拒绝)、run_in_background |
PowerShell | 与 Bash 相同,命令在 command 里 |
Write | file_path、content |
Edit | file_path、old_string、new_string、replace_all |
Read | file_path、可选 offset、limit |
Glob | pattern、可选 path |
Grep | pattern、可选 path、glob、output_mode(content/files_with_matches/count)、-i、multiline |
WebFetch | url、prompt |
WebSearch | query、可选 allowed_domains、blocked_domains |
Agent | prompt、description、subagent_type、可选 model |
AskUserQuestion | questions(一到四个选择题,各含 question、header、options、multiSelect);answers 可选,由 Hook 通过 updatedInput 提供 |
ExitPlanMode | plan(来自磁盘计划文件的 Markdown)、planFilePath;allowedPrompts 已弃用被忽略 |
检查 shell 命令的 Hook 要匹配 Bash|PowerShell 才能同时覆盖两者:在 Windows 上启用了 PowerShell 工具的地方,Claude 把 PowerShell 当主 shell;没有 Git Bash 的 Windows 上 Claude Code 根本不注册 Bash 工具,只匹配 Bash 的 Hook 永远不会触发。若开启了 bashEditDiffEnabled 设置,Bash 命令改了 Git 仓库里的文件时 Claude Code 会记录改动,随后的 PostToolUse Hook 在 tool_response.bashEditDiff 里收到变更文件列表(尽力而为、公测中,字段形状可能变:changedFiles 最多 200 个路径,files 最多 5 个文件的 diff,另有 moreFiles、unavailable、skipped、shared 标志说明列表的完整度)。前台 Agent 调用完成后,PostToolUse 的 tool_response 带 status(completed 或 async_launched)、agentId、content、resolvedModel、modelsUsed、totalTokens、totalDurationMs、totalToolUseCount、usage 等子智能体运行遥测;后台子智能体立即返回,没有用量字段。
决策控制:PreToolUse 与其他 Hook 的顶层 decision 不同,它把决定放在 hookSpecificOutput 里:
| 字段 | 说明 |
|---|---|
permissionDecision | "allow" 跳过权限提示(但「没有任何模式会自动批准的操作」以及 AskUserQuestion、ExitPlanMode 除外);"deny" 阻止调用;"ask" 强制向用户弹出权限提示;"defer" 见下文 |
permissionDecisionReason | ask 时显示给用户(不给 Claude);deny 时显示给 Claude;allow/defer 只写入调试日志 |
updatedInput | 在执行前修改工具输入参数;替换整个输入对象,所以要连同未修改的字段一起返回 |
additionalContext | 与工具结果一起加入 Claude 上下文的字符串;defer 时忽略 |
多个 PreToolUse Hook 返回不同决定时优先级为 deny > defer > ask > allow。退出 2 阻止的方式与 "deny" 等价(Claude 看到 stderr 作为拒绝原因)。Hook 返回 "ask" 时,弹给用户的权限提示会标注 Hook 来源([settings] 等);它在 auto 模式下也会强制出现权限提示(分类器仍可拒绝,但不能无声批准)。被服务器用 _meta["anthropic/requiresUserInteraction"] 标记的 MCP 工具更严格:Hook 不能用 allow 跳过它的批准提示。PreToolUse 过去用顶层 decision/reason,现已弃用,改用 hookSpecificOutput.permissionDecision 和 permissionDecisionReason。
延后工具调用(defer):"defer" 面向把 claude -p 作为子进程运行并读取其 JSON 输出的集成(如 Agent SDK 应用或基于 Claude Code 的自定义界面),让调用方在某个工具调用处暂停 Claude、自己处理后再恢复。典型是 AskUserQuestion:Claude 想问用户,但没有终端可答。流程:Claude 调用 AskUserQuestion,PreToolUse Hook 触发;Hook 返回 permissionDecision: "defer",工具不执行,进程以 stop_reason: "tool_deferred" 退出并把待处理调用保存在转录里;调用方从 SDK 结果里读到 deferred_tool_use(含工具的 id、name、input),在自己的界面里向用户提问;然后运行 claude -p --resume(带同样的 permission host),同一个工具调用再次触发 PreToolUse,Hook 返回 allow 并把答案放进 updatedInput,工具执行、Claude 继续。没有超时或重试上限,会话保存在磁盘上直到恢复(受 cleanupPeriodDays 清理限制)。defer 只在该轮 Claude 只发起一个工具调用时有效,多个并发调用时被忽略并给出警告;恢复时若被延后的工具已不可用,进程以 stop_reason: "tool_deferred_unavailable" 和 is_error: true 退出。在计划模式下恢复要同时传 --permission-prompt-tool;-p 恢复时不会还原其他存储的权限模式,需要重新传 --permission-mode。
PermissionRequest
Claude Code 即将向你请求使用某工具的权限时运行;在无法显示提示的会话(如非交互模式里的后台子智能体)里仍会运行,没有 Hook 返回决定就拒绝该工具调用。用它代表用户允许或拒绝。想在 Claude 请求权限的那一刻得到信号用这个事件;Notification 的 permission_prompt 类型只在提示已等待约六秒后才发。沙箱命令的网络请求不会触发 PermissionRequest(那类提示用 permission_prompt 通知)。matcher 匹配工具名,同 PreToolUse。输入:tool_name、tool_input(没有 tool_use_id),MCP 工具另有 mcp_server,以及可选的 permission_suggestions 数组(不是你所见选项的精确清单,各对话框自己构建选项)。PreToolUse 在每次工具调用前运行,PermissionRequest 只在即将询问你、或本会被自动拒绝时运行。
决策控制:Hook 返回 decision 对象:
| 字段 | 说明 |
|---|---|
behavior | "allow" 授予、"deny" 拒绝;deny 和 ask 规则仍会被评估,所以 allow 不会覆盖匹配的 deny 规则 |
updatedInput | 仅 allow:执行前修改输入(替换整个输入对象),修改后的输入会被重新评估 |
updatedPermissions | 仅 allow:要应用的权限更新条目数组,如加一条 allow 规则或改变会话权限模式 |
message | 仅 deny:告诉 Claude 为何被拒绝 |
interrupt | 仅 deny:为 true 时停止 Claude |
没有 decision 对象就退出 2 会保持权限流程不变,stderr 被丢弃;只有 decision 对象能授予或拒绝。权限更新条目(updatedPermissions 输出和 permission_suggestions 输入共用)按 type 区分:addRules/replaceRules/removeRules(rules 为 {toolName, ruleContent?} 数组,省略 ruleContent 即匹配整个工具,behavior 取 allow/deny/ask,另有 destination)、setMode(mode 取 default、auto、acceptEdits、dontAsk、bypassPermissions、plan,manual 是 default 的别名)、addDirectories/removeDirectories(directories 路径数组)。setMode 为 bypassPermissions 只在你启动会话时已让绕过模式可用的情况下生效,且 bypassPermissions 永远不会作为 defaultMode 持久化。destination:session(仅内存,会话结束丢弃)、localSettings(.claude/settings.local.json)、projectSettings(.claude/settings.json)、userSettings(~/.claude/settings.json)。Hook 可以把收到的某条 permission_suggestions 原样作为自己的 updatedPermissions 输出。
PostToolUse
工具成功完成后立即运行,matcher 同 PreToolUse;要对所有工具生效就省略 matcher 或写 "*",Hook 可以自己发现改了什么(比如运行 git status --porcelain,它能列出 git diff 漏掉的未跟踪文件);工具失败的情况要另在 PostToolUseFailure 下加同样的 Hook。想在文件变化时无论谁写的都运行,用 FileChanged。输入:tool_input(发给工具的参数)和 tool_response(工具返回的结果,各工具的 schema 不同),另有可选的 duration_ms(执行耗时,不含权限提示和 PreToolUse Hook)。决策控制:
| 字段 | 说明 |
|---|---|
decision | "block" 把 reason 附在工具结果旁;Claude 仍能看到原始输出,要替换用 updatedToolOutput |
reason | decision 为 block 时给 Claude 的说明 |
additionalContext | 与工具结果一起加入 Claude 上下文的字符串 |
classifierContext | 给 auto 模式分类器(而不是 Claude)的简短结果说明 |
updatedToolOutput | 在发给 Claude 之前替换工具输出,值必须匹配该工具的输出形状(内置工具返回结构化对象而不是纯字符串,如 Bash 返回含 stdout、stderr、interrupted 等的对象) |
updatedMCPToolOutput | 仅替换 MCP 工具输出;优先用对所有工具都有效的 updatedToolOutput |
updatedToolOutput 只改变 Claude 看到的内容:Hook 触发时工具已经运行,写下的文件、执行的命令和发出的网络请求都已生效。为 auto 模式分类器注解结果:classifierContext 把一条简短的说明发给分类器;对来自设置文件、插件、Skill 和智能体前置信息的 Hook,分类器把它当作未经验证的应用提供的上下文,永远不据此确立授权;作为 Agent SDK 内进程回调返回时权重更高。限制:同一次工具调用的所有 Hook 的说明合计上限 2000 字符、超出截断;后台运行的 Hook 的该字段被忽略(响应到达时 Claude Code 已记录了结果);分类器记录里省略只读查询(如读文件和搜索),附在这类调用上的说明被丢弃;与 updatedToolOutput 一起使用时要在同一个响应里返回两个字段。不要把不受信任的工具输出或第三方文本复制进 classifierContext。
PostToolUseFailure
开始执行的工具失败时运行:工具抛出错误,或 MCP 工具返回错误结果;用于记录失败、发告警或给 Claude 提供纠正反馈。matcher 同 PreToolUse。不会对执行前就被拒绝的调用触发:未知工具名、未通过 schema 或工具特定校验的输入、权限被拒绝(校验拒绝作为 tool_use_error 结果返回,发生在 Hook 之前)。输入:与 PostToolUse 相同的 tool_name、tool_input,加顶层错误信息:error(描述出了什么问题的字符串,格式因工具而异,通常与 Claude 收到的失败结果相同)、可选 is_interrupt(失败以中止而不是工具报告的错误形式到达时为真)、可选 duration_ms。对 Bash 和 PowerShell,命令运行并退出时第一行是 Exit code N,随后是 stdout 与 stderr 交错的输出;Claude Code 无法启动 shell 进程时只有一条没有退出码行的失败消息;过长字符串会在 ... [N characters truncated] ... 标记处从中间截断,也可能插入 Command timed out after 2m 0s 之类的行。决策控制:只有 additionalContext(与错误一起加入 Claude 上下文)。
PostToolBatch
一批工具调用全部完成后、Claude Code 向模型发送下一次请求之前运行一次。PostToolUse 对每个工具触发一次(Claude 并行调用工具时会并发触发),而 PostToolBatch 带着完整批次恰好触发一次,所以是注入「取决于这一组工具而不是任何单个工具」的上下文的合适位置。没有 matcher。输入:tool_calls 数组,描述批次里每个调用;其中 tool_response 与模型收到的 tool_result 块内容相同(序列化字符串或内容块数组),与 PostToolUse 传结构化 Output 对象不同。决策控制:additionalContext(在下一次模型调用前注入一次);返回 decision: "block" 或 continue: false 会在下一次模型调用之前停止智能体循环,阻塞消息来自 JSON 的 reason 或 stopReason,或退出 2 的 stderr。
PermissionDenied
auto 模式拒绝工具调用时运行,包括没有分类器判决就拒绝的情况(与 auto 模式分开的安全检查拒绝了分类器自己的请求,或其响应解析失败)。只在 auto 模式触发:你手动拒绝权限对话框、PreToolUse Hook 阻止调用、或 deny 规则匹配时都不触发。matcher 同 PreToolUse。输入:tool_name、tool_input、tool_use_id、reason(拒绝原因;分类器判决时多数会话里以方括号写出匹配的规则,如 [Data Exfiltration]);MCP 工具另有 mcp_server。输出:hookSpecificOutput.retry: true 让 Claude Code 往对话里加一条消息,告诉模型可以重试被拒绝的工具调用;它并不撤销拒绝本身。分类器对该操作没有判决时(响应解析失败或安全检查拒绝了请求)retry: true 被忽略。