- 阻止危险操作在执行前进行,如破坏性 shell 命令或未授权的文件访问
- 记录和审计每个工具调用,用于合规性、调试或分析
- 转换输入和输出以清理数据、注入凭证或重定向文件路径
- 要求人工批准敏感操作,如数据库写入或 API 调用
- 跟踪会话生命周期以管理状态、清理资源或发送通知
Hooks 如何工作
事件触发
代理执行期间发生某事,SDK 触发事件:工具即将被调用(
PreToolUse)、工具返回结果(PostToolUse)、子代理启动或停止、代理空闲或执行完成。请参阅完整事件列表。SDK 收集已注册的 hooks
SDK 检查为该事件类型注册的 hooks。这包括您在
options.hooks 中传递的回调 hooks 和来自设置文件的 shell 命令 hooks,当相应的 settingSources 或 setting_sources 条目启用时(默认 query() 选项就是这样)。匹配器过滤哪些 hooks 运行
如果 hook 有
matcher 模式(如 "Write|Edit"),SDK 会针对事件的目标(例如工具名称)测试它。没有匹配器的 hooks 对该类型的每个事件都运行。回调函数执行
每个匹配的 hook 的回调函数接收有关正在发生的事情的输入:工具名称、其参数、会话 ID 和其他事件特定的详细信息。
您的回调返回决定
执行任何操作(日志记录、API 调用、验证)后,您的回调返回一个输出对象,告诉代理该做什么:允许操作、阻止它、修改输入或将上下文注入到对话中。
PreToolUse hook(步骤 1),带有 "Write|Edit" 匹配器(步骤 3),因此回调仅对文件写入工具触发。触发时,回调接收工具的输入(步骤 4),检查文件路径是否针对 .env 文件,并返回 permissionDecision: "deny" 以阻止操作(步骤 5):
可用的 hooks
SDK 为代理执行的不同阶段提供 hooks。某些 hooks 在两个 SDK 中都可用,而其他 hooks 仅在 TypeScript 中可用。| Hook 事件 | Python SDK | TypeScript SDK | 触发条件 | 示例用例 |
|---|---|---|---|---|
PreToolUse | 是 | 是 | 工具调用请求(可以阻止或修改) | 阻止危险的 shell 命令 |
PostToolUse | 是 | 是 | 工具执行结果 | 将所有文件更改记录到审计跟踪 |
PostToolUseFailure | 是 | 是 | 工具执行失败 | 处理或记录工具错误 |
PostToolBatch | 否 | 是 | 一整批工具调用解决,每批一次,在下一个模型调用之前 | 为整个批次注入约定 |
UserPromptSubmit | 是 | 是 | 用户提示提交 | 将额外上下文注入到提示中 |
MessageDisplay | 否 | 是 | 助手消息包含文本完成,每条消息一次,包含完整消息文本 | 编辑或重新格式化显示的文本而不改变记录 |
Stop | 是 | 是 | 代理执行停止 | 在退出前保存会话状态 |
SubagentStart | 是 | 是 | 子代理初始化 | 跟踪并行任务生成 |
SubagentStop | 是 | 是 | 子代理完成 | 聚合来自并行任务的结果 |
PreCompact | 是 | 是 | 对话压缩请求 | 在总结前存档完整记录 |
PermissionRequest | 是 | 是 | 权限对话将显示 | 自定义权限处理 |
SessionStart | 否 | 是 | 会话初始化 | 初始化日志记录和遥测 |
SessionEnd | 否 | 是 | 会话终止 | 清理临时资源 |
Notification | 是 | 是 | 代理状态消息 | 将代理状态更新发送到 Slack 或 PagerDuty |
Setup | 否 | 是 | 会话设置/维护 | 运行初始化任务 |
TeammateIdle | 否 | 是 | 队友变为空闲 | 重新分配工作或通知 |
TaskCompleted | 否 | 是 | 后台任务完成 | 聚合来自并行任务的结果 |
ConfigChange | 否 | 是 | 配置文件更改 | 动态重新加载设置 |
WorktreeCreate | 否 | 是 | Git worktree 创建 | 跟踪隔离的工作区 |
WorktreeRemove | 否 | 是 | Git worktree 移除 | 清理工作区资源 |
配置 hooks
要配置 hook,请在您的代理选项的hooks 字段中传递它(Python 中的 ClaudeAgentOptions,TypeScript 中的 options 对象):
hooks 选项是一个字典(Python)或对象(TypeScript),其中:
匹配器
使用匹配器来过滤您的回调何时触发。matcher 字段根据 hook 事件类型匹配不同的值。例如,基于工具的 hooks 匹配工具名称,而 Notification hooks 匹配通知类型。请参阅 Claude Code hooks 参考以获取每个事件类型的匹配器值的完整列表。
SDK 匹配器遵循与设置文件中的匹配器相同的规则:仅包含字母、数字、_ 和 | 的匹配器作为精确字符串进行比较,| 分隔替代项,因此 Write|Edit 精确匹配这两个工具。* 的匹配器、空字符串或完全省略匹配器会匹配事件的每次出现;包含任何其他字符的匹配器被评估为正则表达式,因此 ^mcp__ 匹配每个 MCP 工具。像 mcp__memory 这样的匹配器仅包含字母和下划线,因此它作为精确字符串进行比较,不匹配任何工具;使用 mcp__memory__.* 来匹配来自该服务器的每个工具。
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
matcher | string | undefined | 针对事件的过滤字段匹配的模式,遵循上述比较规则。对于工具 hooks,这是工具名称。内置工具包括 Bash、Read、Write、Edit、Glob、Grep、WebFetch、Agent 等(请参阅工具输入类型以获取完整列表)。MCP 工具使用模式 mcp__<server>__<action>。 |
hooks | HookCallback[] | - | 必需。当模式匹配时执行的回调函数数组 |
timeout | number | 60 | 超时时间(秒) |
matcher 模式来针对特定工具。带有 'Bash' 的匹配器仅对 Bash 命令运行,而省略模式会为事件的每次出现运行您的回调。请注意,对于基于工具的 hooks,匹配器仅按工具名称过滤,而不是按文件路径或其他参数。要按文件路径过滤,请在回调内检查 tool_input.file_path。
回调函数
输入
每个 hook 回调接收三个参数:- 输入数据: 一个包含事件详细信息的类型化对象。每个 hook 类型都有自己的输入形状(例如,
PreToolUseHookInput包括tool_name和tool_input,而NotificationHookInput包括message)。请参阅 TypeScript 和 Python SDK 参考中的完整类型定义。- 所有 hook 输入共享
session_id、cwd和hook_event_name。 - 当 hook 在子代理内触发时,
agent_id和agent_type被填充。在 TypeScript 中,这些在基础 hook 输入上,对所有 hook 类型都可用。在 Python 中,它们仅在PreToolUse、PostToolUse和PostToolUseFailure上。
- 所有 hook 输入共享
- 工具使用 ID(
str | None/string | undefined):关联同一工具调用的PreToolUse和PostToolUse事件。 - 上下文: 在 TypeScript 中,包含用于取消的
signal属性(AbortSignal)。在 Python 中,此参数保留供将来使用。
输出
您的回调返回一个具有两类字段的对象:- 顶级字段在每个事件上的工作方式相同:
systemMessage向用户显示消息,continue(Python 中的continue_)确定代理在此 hook 后是否继续运行。 hookSpecificOutput控制当前操作。内部的字段取决于 hook 事件类型。对于PreToolUsehooks,这是您设置permissionDecision("allow"、"deny"、"ask"或"defer")、permissionDecisionReason和updatedInput的地方。返回"defer"结束查询,以便您可以稍后恢复它。对于PostToolUsehooks,您可以设置additionalContext以将信息附加到工具结果。要在 Claude 看到之前替换工具的输出,请设置updatedToolOutput,这适用于两个 SDK 中的任何工具。较旧的updatedMCPToolOutput字段仅替换 MCP 工具输出,已弃用。
{} 以允许操作而不进行更改。SDK 回调 hooks 使用与 Claude Code shell 命令 hooks 相同的 JSON 输出格式,其中记录了每个字段和事件特定的选项。对于 SDK 类型定义,请参阅 TypeScript 和 Python SDK 参考。
当多个 hooks 或权限规则适用时,deny 优先于 defer,defer 优先于 ask,ask 优先于 allow。如果任何 hook 返回
deny,操作将被阻止,无论其他 hooks 如何。异步输出
默认情况下,代理在您的 hook 返回前等待。如果您的 hook 执行副作用(日志记录、发送 webhook)并且不需要影响代理的行为,您可以改为返回异步输出。这告诉代理立即继续,而不等待 hook 完成:| 字段 | 类型 | 描述 |
|---|---|---|
async | true | 表示异步模式。代理继续而不等待。在 Python 中,使用 async_ 以避免保留关键字。 |
asyncTimeout | number | 后台操作的可选超时时间(毫秒) |
异步输出无法阻止、修改或将上下文注入到操作中,因为代理已经继续。仅将它们用于日志记录、指标或通知等副作用。
示例
修改工具输入
此示例拦截 Write 工具调用并重写file_path 参数以添加 /sandbox 前缀,将所有文件写入重定向到沙箱目录。回调返回带有修改路径的 updatedInput 和 permissionDecision: 'allow' 以自动批准重写的操作:
使用
updatedInput 时,您还必须包括 permissionDecision: 'allow' 以自动批准修改的输入,或 permissionDecision: 'ask' 以将其显示给用户。使用 'defer' 时,updatedInput 会被忽略。始终返回新对象而不是改变原始 tool_input。添加上下文并阻止工具
此示例阻止写入/etc 目录的操作,并向模型和用户解释原因:
permissionDecision: 'deny'停止工具调用。permissionDecisionReason告诉模型原因,以便它避免重试。systemMessage向用户显示发生了什么。
自动批准特定工具
默认情况下,代理可能在使用某些工具前提示权限。此示例通过返回permissionDecision: 'allow' 自动批准只读文件系统工具(Read、Glob、Grep),让它们无需用户确认即可运行,同时让所有其他工具受到正常权限检查:
注册多个 hooks
当事件触发时,所有匹配的 hooks 并行运行。对于权限决策,最严格的结果获胜:单个deny 会阻止工具调用,无论其他 hooks 返回什么。由于完成顺序是不确定的,请编写每个 hook 以独立行动,而不是依赖另一个 hook 已运行。
下面的示例为每个工具调用注册三个独立检查:
使用多工具匹配器过滤
使用多工具匹配器在相关工具间共享一个回调。此示例注册三个具有不同范围的匹配器:- 管道分隔的精确列表(
Write|Edit|Delete)仅对文件修改工具触发file_security_hook。 - 正则表达式(
^mcp__)对任何名称以mcp__开头的 MCP 工具触发mcp_audit_hook。 - 省略的匹配器对每个工具调用(无论名称如何)触发
global_logger。
跟踪子代理活动
使用SubagentStop hooks 监控子代理何时完成其工作。请参阅 TypeScript 和 Python SDK 参考中的完整输入类型。此示例在每次子代理完成时记录摘要:
从 hooks 发出 HTTP 请求
Hooks 可以执行异步操作,如 HTTP 请求。在您的 hook 内捕获错误,而不是让它们传播,因为未处理的异常可能会中断代理。 此示例在每个工具完成后发送 webhook,记录哪个工具运行以及何时运行。hook 捕获错误,以便失败的 webhook 不会中断代理:将通知转发到 Slack
使用Notification hooks 从代理接收系统通知并将其转发到外部服务。通知针对事件类型触发,例如:
permission_prompt当 Claude 需要权限时idle_prompt当 Claude 等待输入时auth_success当身份验证完成时elicitation_dialog、elicitation_complete和elicitation_response用于用户提示引导流程
message 字段,以及可选的 title。
此示例将每个通知转发到 Slack 频道。它需要一个 Slack 传入 webhook URL,您可以通过将应用添加到您的 Slack 工作区并启用传入 webhooks 来创建:
修复常见问题
Hook 未触发
- 验证 hook 事件名称正确且区分大小写(
PreToolUse,而不是preToolUse) - 检查您的匹配器模式是否与工具名称完全匹配
- 确保 hook 在
options.hooks中的正确事件类型下 - 对于非工具 hooks,如
Stop和SubagentStop,匹配器匹配不同的字段(请参阅匹配器模式) - 当代理达到
max_turns限制时,hooks 可能不会触发,因为会话在 hooks 可以执行前结束
匹配器未按预期过滤
匹配器仅匹配工具名称,而不是文件路径或其他参数。要按文件路径过滤,请在您的 hook 内检查tool_input.file_path:
Hook 超时
- 增加
HookMatcher配置中的timeout值 - 在 TypeScript 中使用第三个回调参数中的
AbortSignal来优雅地处理取消
工具意外被阻止
- 检查所有
PreToolUsehooks 是否返回permissionDecision: 'deny' - 向您的 hooks 添加日志记录以查看它们返回的
permissionDecisionReason - 验证匹配器模式不会太宽泛(空匹配器匹配所有工具)
修改的输入未应用
-
确保
updatedInput在hookSpecificOutput内,而不是在顶级: -
返回
permissionDecision: 'allow'以自动批准修改的输入,或返回'ask'以向用户显示以供批准 -
在
hookSpecificOutput中包括hookEventName以识别输出针对的 hook 类型
Python 中不可用会话 hooks
SessionStart 和 SessionEnd 可以在 TypeScript 中注册为 SDK 回调 hooks,但在 Python SDK 中不可用(HookEvent 省略了它们)。在 Python 中,它们仅作为shell 命令 hooks 在设置文件中定义(例如 .claude/settings.json)。要从您的 SDK 应用程序加载 shell 命令 hooks,请使用 setting_sources 或 settingSources 包括适当的设置源:
client.receive_response() 的第一条消息作为您的触发器。
子代理权限提示倍增
生成多个子代理时,每个子代理可能会单独请求权限。子代理不会自动继承父代理权限。要避免重复提示,请使用PreToolUse hooks 自动批准特定工具,或配置适用于子代理会话的权限规则。
子代理的递归 hook 循环
生成子代理的UserPromptSubmit hook 如果这些子代理触发相同的 hook,可能会创建无限循环。要防止这种情况:
- 在生成子代理前检查 hook 输入中的子代理指示符
- 使用共享变量或会话状态来跟踪您是否已在子代理内
- 将 hooks 范围限制为仅对顶级代理会话运行
systemMessage 未出现在输出中
systemMessage 字段向用户显示消息,而不是模型。默认情况下,SDK 不会在消息流中显示 hook 输出,因此除非您设置 includeHookEvents(Python 中为 include_hook_events),否则消息可能不会出现。要改为将上下文传递给模型,请返回 additionalContext。
如果您需要可靠地将 hook 决定呈现给您的应用程序,请单独记录它们或使用专用输出通道。
相关资源
- Claude Code hooks 参考:完整的 JSON 输入/输出架构、事件文档和匹配器模式
- Claude Code hooks 指南:shell 命令 hook 示例和演练
- TypeScript SDK 参考:hook 类型、输入/输出定义和配置选项
- Python SDK 参考:hook 类型、输入/输出定义和配置选项
- 权限:控制您的代理可以做什么
- 自定义工具:构建工具以扩展代理功能