跳转到主要内容
Hooks 是回调函数,用于响应代理事件(如工具被调用、会话启动或执行停止)运行您的代码。使用 hooks,您可以:
  • 阻止危险操作在执行前进行,如破坏性 shell 命令或未授权的文件访问
  • 记录和审计每个工具调用,用于合规性、调试或分析
  • 转换输入和输出以清理数据、注入凭证或重定向文件路径
  • 要求人工批准敏感操作,如数据库写入或 API 调用
  • 跟踪会话生命周期以管理状态、清理资源或发送通知
本指南涵盖 hooks 的工作原理、如何配置它们,并提供常见模式的示例,如阻止工具、修改输入和转发通知。

Hooks 如何工作

1

事件触发

代理执行期间发生某事,SDK 触发事件:工具即将被调用(PreToolUse)、工具返回结果(PostToolUse)、子代理启动或停止、代理空闲或执行完成。请参阅完整事件列表
2

SDK 收集已注册的 hooks

SDK 检查为该事件类型注册的 hooks。这包括您在 options.hooks 中传递的回调 hooks 和来自设置文件的 shell 命令 hooks,当相应的 settingSourcessetting_sources 条目启用时(默认 query() 选项就是这样)。
3

匹配器过滤哪些 hooks 运行

如果 hook 有 matcher 模式(如 "Write|Edit"),SDK 会针对事件的目标(例如工具名称)测试它。没有匹配器的 hooks 对该类型的每个事件都运行。
4

回调函数执行

每个匹配的 hook 的回调函数接收有关正在发生的事情的输入:工具名称、其参数、会话 ID 和其他事件特定的详细信息。
5

您的回调返回决定

执行任何操作(日志记录、API 调用、验证)后,您的回调返回一个输出对象,告诉代理该做什么:允许操作、阻止它、修改输入或将上下文注入到对话中。
以下示例将这些步骤组合在一起。它注册一个 PreToolUse hook(步骤 1),带有 "Write|Edit" 匹配器(步骤 3),因此回调仅对文件写入工具触发。触发时,回调接收工具的输入(步骤 4),检查文件路径是否针对 .env 文件,并返回 permissionDecision: "deny" 以阻止操作(步骤 5):

可用的 hooks

SDK 为代理执行的不同阶段提供 hooks。某些 hooks 在两个 SDK 中都可用,而其他 hooks 仅在 TypeScript 中可用。

配置 hooks

要配置 hook,请在您的代理选项的 hooks 字段中传递它(Python 中的 ClaudeAgentOptions,TypeScript 中的 options 对象):
hooks 选项是一个字典(Python)或对象(TypeScript),其中:

匹配器

使用匹配器来过滤您的回调何时触发。matcher 字段根据 hook 事件类型匹配不同的值。例如,基于工具的 hooks 匹配工具名称,而 Notification hooks 匹配通知类型。请参阅 Claude Code hooks 参考以获取每个事件类型的匹配器值的完整列表。 SDK 匹配器遵循与设置文件中的匹配器相同的规则。仅包含字母、数字、_-、空格、,| 的匹配器作为精确字符串进行比较,替代项由 |, 分隔,可选的周围空格,因此 Write|EditWrite, Edit 各自精确匹配这两个工具,code-reviewer 仅匹配该代理类型。* 的匹配器、空字符串或完全省略匹配器会匹配事件的每次出现。 包含任何其他字符的匹配器被评估为非锚定正则表达式,因此 ^mcp__ 匹配每个 MCP 工具,Edit.* 匹配 EditNotebookEdit。当您需要全字符串匹配时,用 ^$ 包装正则表达式。 mcp__memorymcp__brave-search 这样的匹配器仅包含精确匹配字符,因此它作为精确字符串进行比较,不匹配任何工具;使用 mcp__memory__.* 来匹配来自该服务器的每个工具。 精确匹配集中的连字符需要 Claude Code 运行时 v2.1.195 或更高版本。在较早的版本中,像 code-reviewer 这样的带连字符的名称被评估为非锚定正则表达式,必须锚定为 ^code-reviewer$ 才能精确匹配。 尽可能使用 matcher 模式来针对特定工具。带有 'Bash' 的匹配器仅对 Bash 命令运行,而省略模式会为事件的每次出现运行您的回调。 对于基于工具的 hooks,匹配器仅按工具名称过滤,而不是按文件路径或其他参数。要按文件路径过滤,请在回调内检查 tool_input.file_path
发现工具名称: 请参阅工具输入类型以获取内置工具名称的完整列表,或添加没有匹配器的 hook 来记录您的会话进行的所有工具调用。MCP 工具命名: MCP 工具始终以 mcp__ 开头,后跟服务器名称和操作:mcp__<server>__<action>。例如,如果您配置一个名为 playwright 的服务器,其工具将被命名为 mcp__playwright__browser_screenshotmcp__playwright__browser_click 等。服务器名称来自您在 mcpServers 配置中使用的键。

回调函数

输入

每个 hook 回调接收三个参数:
  • 输入数据: 一个包含事件详细信息的类型化对象。每个 hook 类型都有自己的输入形状。例如,PreToolUseHookInput 包括 tool_nametool_input,而 NotificationHookInput 包括 message。请参阅 TypeScriptPython SDK 参考中的完整类型定义。
    • 所有 hook 输入共享 session_idcwdhook_event_name
    • 当 hook 在子代理内触发时,agent_idagent_type 被填充。在 TypeScript 中,这些在基础 hook 输入上,对所有 hook 类型都可用。在 Python 中,它们是 PreToolUsePostToolUsePostToolUseFailurePermissionRequest 上的可选字段,以及 SubagentStartSubagentStop 上的必需字段。
  • 工具使用 IDstr | None / string | undefined):关联同一工具调用的 PreToolUsePostToolUse 事件。
  • 上下文: 在 TypeScript 中,包含用于取消的 signal 属性(AbortSignal)。在 Python 中,此参数保留供将来使用。

输出

您的回调返回一个具有两类字段的对象:
  • 顶级字段在每个事件上的工作方式相同:systemMessage 向用户显示消息,continue(Python 中的 continue_)确定代理在此 hook 后是否继续运行。
  • hookSpecificOutput 控制当前操作。内部的字段取决于 hook 事件类型。对于 PreToolUse hooks,这是您设置 permissionDecision"allow""deny""ask""defer")、permissionDecisionReasonupdatedInput 的地方。返回 "defer" 结束查询,以便您可以稍后恢复它。对于 PostToolUse hooks,您可以设置 additionalContext 以将信息附加到工具结果。要在 Claude 看到之前替换工具的输出,请设置 updatedToolOutput,这适用于两个 SDK 中的任何工具。较旧的 updatedMCPToolOutput 字段仅替换 MCP 工具输出,已弃用。
返回 {} 以允许操作而不进行更改。SDK 回调 hooks 使用与 Claude Code shell 命令 hooks 相同的 JSON 输出格式,其中记录了每个字段和事件特定的选项。对于 SDK 类型定义,请参阅 TypeScriptPython SDK 参考。
当多个 hooks 或权限规则适用时,deny 优先于 deferdefer 优先于 askask 优先于 allow。如果任何 hook 返回 deny,操作将被阻止,无论其他 hooks 如何。

异步输出

默认情况下,代理在您的 hook 返回前等待。如果您的 hook 执行副作用,例如日志记录或发送 webhook,并且不需要影响代理的行为,您可以改为返回异步输出。这告诉代理立即继续,而不等待 hook 完成:
异步输出无法阻止、修改或将上下文注入到操作中,因为代理已经继续。仅将它们用于日志记录、指标或通知等副作用。

示例

修改工具输入

此示例拦截 Write 工具调用并重写 file_path 参数以添加 /sandbox 前缀,将所有文件写入重定向到沙箱目录。回调返回带有修改路径的 updatedInputpermissionDecision: '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 监控子代理何时完成其工作。请参阅 TypeScriptPython SDK 参考中的完整输入类型。此示例在每次子代理完成时记录摘要:

从 hooks 发出 HTTP 请求

Hooks 可以执行异步操作,如 HTTP 请求。在您的 hook 内捕获错误,而不是让它们传播,因为未处理的异常可能会中断代理。 此示例在每个工具完成后发送 webhook,记录哪个工具运行以及何时运行。hook 捕获错误,以便失败的 webhook 不会中断代理:

将通知转发到 Slack

使用 Notification hooks 从代理接收系统通知并将其转发到外部服务。通知针对事件类型触发,例如:
  • permission_prompt 当 Claude 需要权限时
  • idle_prompt 当 Claude 等待输入时
  • auth_success 当身份验证完成时
  • elicitation_dialogelicitation_completeelicitation_response 用于用户提示引导流程
每个通知包括一个带有人类可读描述的 message 字段,以及可选的 title 此示例将每个通知转发到 Slack 频道。它需要一个 Slack 传入 webhook URL,您可以通过将应用添加到您的 Slack 工作区并启用传入 webhooks 来创建:

修复常见问题

Hook 未触发

  • 验证 hook 事件名称正确且区分大小写(PreToolUse,而不是 preToolUse
  • 检查您的匹配器模式是否与工具名称完全匹配
  • 确保 hook 在 options.hooks 中的正确事件类型下
  • 对于支持匹配器的非工具 hooks,如 NotificationSubagentStop,匹配器匹配不同的字段,而 Stop 完全忽略匹配器(请参阅匹配器模式
  • 当代理达到 max_turns 限制时,hooks 可能不会触发,因为会话在 hooks 可以执行前结束

匹配器未按预期过滤

匹配器仅匹配工具名称,而不是文件路径或其他参数。要按文件路径过滤,请在您的 hook 内检查 tool_input.file_path

Hook 超时

  • 增加 HookMatcher 配置中的 timeout
  • 在 TypeScript 中使用第三个回调参数中的 AbortSignal 来优雅地处理取消
超过超时时间的 UserPromptSubmitUserPromptExpansion 回调会用超时消息阻止该提示,会话继续进行。在回调待处理时中断查询会取消待处理的工具调用。在 v2.1.208 之前,这些事件上的回调超时会以 error_during_execution 结束查询,在待处理的 PreToolUse 回调期间中断可能会让工具调用继续进行。

工具意外被阻止

  • 检查所有 PreToolUse hooks 是否返回 permissionDecision: 'deny'
  • 向您的 hooks 添加日志记录以查看它们返回的 permissionDecisionReason
  • 验证匹配器模式不会太宽泛:空匹配器匹配所有工具

修改的输入未应用

  • 确保 updatedInputhookSpecificOutput 内,而不是在顶级:
  • 返回 permissionDecision: 'allow' 以自动批准修改的输入,或返回 'ask' 以向用户显示以供批准
  • hookSpecificOutput 中包括 hookEventName 以识别输出针对的 hook 类型

Python 中不可用会话 hooks

SessionStartSessionEnd 可以在 TypeScript 中注册为 SDK 回调 hooks,但在 Python SDK 中不可用,因为其 HookEvent 类型省略了它们。在 Python 中,它们仅作为shell 命令 hooks在设置文件中定义,例如 .claude/settings.json。要从您的 SDK 应用程序加载 shell 命令 hooks,请使用 setting_sourcessettingSources 包括适当的设置源:
要改为运行初始化逻辑作为 Python SDK 回调,请使用 client.receive_response() 的第一条消息作为您的触发器。

子代理权限提示倍增

生成多个子代理时,每个子代理可能会单独请求权限。子代理不会自动继承父代理权限。要避免重复提示,请使用 PreToolUse hooks 自动批准特定工具,或配置适用于子代理会话的权限规则。

子代理的递归 hook 循环

生成子代理的 UserPromptSubmit hook 如果这些子代理触发相同的 hook,可能会创建无限循环。要防止这种情况:
  • 在生成子代理前检查 hook 输入中的子代理指示符
  • 使用共享变量或会话状态来跟踪您是否已在子代理内
  • 将 hooks 范围限制为仅对顶级代理会话运行

systemMessage 未出现在输出中

systemMessage 字段向用户显示消息,而不是模型。默认情况下,SDK 仅在消息流中为 SessionStartSetup hooks 显示 hook 输出,因此除非您设置 includeHookEvents(Python 中为 include_hook_events),否则来自任何其他 hook 事件的消息不会出现。要改为将上下文传递给模型,请返回 additionalContext 如果您需要可靠地将 hook 决定呈现给您的应用程序,请单独记录它们或使用专用输出通道。