- 阻止危险操作在执行前进行,如破坏性 shell 命令或未授权的文件访问
- 记录和审计工具调用,用于合规性、调试或分析
- 转换输入和输出以清理数据、注入凭证或重定向文件路径
- 要求人工批准敏感操作,如数据库写入或 API 调用
- 跟踪会话生命周期以管理状态、清理资源或发送通知
hook 如何工作
1
事件触发
Agent 执行期间发生某事,SDK 触发事件:工具即将被调用(
PreToolUse)、工具返回结果(PostToolUse)、子代理启动或停止、Agent 空闲或执行完成。请参阅完整事件列表。2
SDK 收集已注册的 hook
SDK 检查为该事件类型注册的 hook。这包括您在
options.hooks 中传递的回调 hook 和来自设置文件的 shell 命令 hook,当相应的 settingSources 或 setting_sources 条目启用时(默认 query() 选项就是这样)。3
匹配器过滤哪些 hook 运行
如果 hook 有
matcher 模式(如 "Write|Edit"),SDK 会针对事件的目标(例如工具名称)测试它。没有匹配器的 hook 对该类型的每个事件都运行。4
回调函数执行
每个匹配的 hook 的回调函数接收有关正在发生的事情的输入:工具名称、其参数、会话 ID 和其他事件特定的详细信息。
5
您的回调返回决定
执行任何操作(日志记录、API 调用、验证)后,您的回调返回一个输出对象,告诉 Agent 该做什么:允许操作、阻止它、修改输入或将上下文注入到对话中。
PreToolUse hook(步骤 1),带有 "Write|Edit" 匹配器(步骤 3),因此回调会针对 Write 和 Edit 工具触发。触发时,回调接收工具的输入(步骤 4),检查文件路径是否针对 .env 文件,并返回 permissionDecision: "deny" 以阻止操作(步骤 5):
file_path 以 /.env 结尾的 Write 或 Edit 调用,不区分字母大小写。在 Windows 上,file_path 使用反斜杠,例如 C:\project\.env,此时该 hook 会允许对 C:\project\.env 的 Write 或 Edit 调用。
可用的 hooks
SDK 为代理执行的不同阶段提供 hooks。某些 hooks 在两个 SDK 中都可用,而其他 hooks 仅在 TypeScript 中可用。配置 hook
要配置 hook,请在您的 Agent 选项的hooks 字段中传递它(Python 中的 ClaudeAgentOptions,TypeScript 中的 options 对象)。此代码段假设您已经定义了一个 hook 回调,例如上面示例中 Python 中的 protect_env_files 或 TypeScript 中的 protectEnvFiles:
hooks 选项是一个字典(Python)或对象(TypeScript),其中:
匹配器
使用匹配器来过滤您的回调何时触发。matcher 字段根据 hook 事件类型匹配不同的值。例如,基于工具的 hook 匹配工具名称,而 Notification hook 匹配通知类型。
SDK 匹配器遵循与设置文件中的匹配器相同的规则。该部分记录了精确字符串和正则表达式评估路径、它们的版本要求以及每个事件类型的匹配器值。
尽可能使用
matcher 模式来针对特定工具。带有 'Bash' 的匹配器仅对 Bash 命令运行,而省略模式会为事件的每次出现运行您的回调。
回调函数
输入
每个 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和PermissionRequest上的可选字段,以及SubagentStart和SubagentStop上的必需字段。
- 所有 hook 输入共享
- 工具使用 ID(
str | None/string | undefined):关联同一工具调用的PreToolUse和PostToolUse事件。 - 上下文: 在 TypeScript 中,包含用于取消的
signal属性(AbortSignal)。在 Python 中,此参数保留供将来使用。
输出
您的回调返回一个具有两类字段的对象:- 顶级字段在每个事件上被接受:
systemMessage向用户显示消息,continue(Python 中的continue_)确定 Agent 在此 hook 后是否继续运行。某些事件会丢弃它们或将它们传递到其他地方。hooks 页面上每个事件的部分说明了它们的去向。 hookSpecificOutput控制当前操作。内部的字段取决于 hook 事件类型:- 对于
PreToolUsehook,这是您设置permissionDecision("allow"、"deny"、"ask"或"defer")、permissionDecisionReason和updatedInput的地方。如果您返回"defer",该轮次将以一条stop_reason为"tool_deferred"的结果消息结束,以便您可以稍后恢复该调用。 - 对于
PostToolUsehook,您可以设置additionalContext以将信息附加到工具结果。要在 Claude 看到之前替换工具的输出,请设置updatedToolOutput,这适用于两个 SDK 中的任何工具。较旧的updatedMCPToolOutput字段仅替换 MCP 工具输出。 - 在 TypeScript SDK 中,
PostToolUse回调也可以返回classifierContext,这是关于工具调用结果的简短说明,用于自动模式权限分类器。因为您的回调在您的应用程序自己的进程中运行,分类器可能会将您在说明中转达的用户声明视为用户意图。该字段需要 TypeScript Agent SDK v0.3.236 或更高版本。为自动模式分类器注释结果涵盖了长度上限、仅同步规则以及不要在说明中放入的内容。
- 对于
{} 以允许操作而不进行更改。SDK 回调 hook 使用与 Claude Code shell 命令 hook 相同的 JSON 输出格式,其中记录了每个字段和事件特定的选项。对于 SDK 类型定义,请参阅 TypeScript 和 Python SDK 参考。
当多个
PreToolUse hook 返回不同的决策时,deny 优先于 defer,defer 优先于 ask,ask 优先于 allow。异步输出
默认情况下,Agent 在您的 hook 返回前等待。如果您的 hook 执行副作用,例如日志记录或发送 webhook,并且不需要影响 Agent 的行为,您可以改为返回异步输出。这告诉 Agent 立即继续,而不等待 hook 完成。在此代码段中,Python 中的send_to_logging_service 和 TypeScript 中的 sendToLoggingService 代表您定义的任何日志记录函数:
异步输出无法阻止、修改或将上下文注入到操作中,因为 Agent 已经继续。仅将它们用于日志记录、指标或通知等副作用。
示例
本节中的几个示例仅显示回调函数。要运行一个示例,请在选项的hooks 字段中的匹配事件下注册回调,如 配置 hooks 中所示。
修改工具输入
此示例拦截 Write 工具调用并重写file_path 参数以添加 /sandbox 前缀。回调返回带有修改路径的 updatedInput 和 permissionDecision: 'allow' 以自动批准重写的操作:
将
updatedInput 与 permissionDecision: 'allow' 配对以自动批准修改的输入,或使用 permissionDecision: 'ask' 将其显示给用户。如果省略 permissionDecision,修改的输入仍然适用并流经正常的权限评估。使用 'defer' 时,updatedInput 会被忽略。始终返回新对象而不是改变原始 tool_input。./sandbox 或 /tmp/sandbox(macOS 不允许创建根级 /sandbox 目录),然后要求代理写入文件:Write 工具在消息流中的结果会显示带有您的沙箱前缀的路径,而不是 Claude 请求的路径。
添加上下文并阻止工具
此示例拒绝file_path 以 /etc 开头的工具调用,并向模型和用户解释原因:
permissionDecision: 'deny'停止工具调用。permissionDecisionReason告诉模型原因。systemMessage向用户显示发生了什么。
PreToolUse 下注册回调,使用 Write|Edit 匹配器,并要求代理在 /etc 下创建文件:Write 工具在消息流中的结果包含 Writing to /etc is not allowed,并且不会创建任何文件。
自动批准特定工具
默认情况下,Agent 可能在使用某些工具前请求权限。此示例通过返回permissionDecision: 'allow' 自动批准只读文件系统工具(Read、Glob、Grep),让它们无需用户确认即可运行(从网络路径读取的操作除外),同时让所有其他工具受到正常权限检查:
注册多个 hooks
当事件触发时,匹配的 hook 并行运行。对于权限决策,以最严格的结果为准。任一 hook 返回的deny 都会阻止工具调用。由于完成顺序是不确定的,请编写每个 hook 以独立行动,而不是依赖另一个 hook 已先运行。
下面的示例注册三个独立检查,每个检查都省略了匹配器。其中的 hook 名称,例如 Python 中的 audit_logger 或 TypeScript 中的 auditLogger,代表您定义的回调:
使用多工具匹配器过滤
使用多工具匹配器在相关工具间共享一个回调。此示例注册三个具有不同范围的匹配器,其中每个 hook 名称代表您定义的回调:- 管道分隔的精确列表(
Write|Edit|NotebookEdit)对 Write、Edit 和 NotebookEdit 工具触发file_security_hook。 - 正则表达式(
^mcp__)对任何名称以mcp__开头的 MCP 工具触发mcp_audit_hook。 - 省略的匹配器触发
global_logger,不按工具名称过滤。
跟踪子代理活动
使用SubagentStop hooks 监控子代理何时完成其工作。请参阅 TypeScript 和 Python SDK 参考中的完整输入类型。此示例在每次子代理完成时记录摘要:
[SUBAGENT] Completed: 行,其中包含子代理的 ID 和脚本路径。
从 hooks 发出 HTTP 请求
Hooks 可以执行异步操作,如 HTTP 请求。在您的 hook 内捕获错误,而不是让它们传播。 此示例在每个工具完成后发送 webhook,记录哪个工具运行以及何时运行。hook 捕获来自失败 webhook 的错误:将通知转发到 Slack
使用Notification hooks 从代理接收系统通知并将其转发到外部服务。在 SDK 会话中,Claude Code 为以下通知类型运行此 hook:
permission_prompt一旦权限请求在您的canUseTool回调 上等待约六秒。需要 TypeScript Agent SDK v0.3.233 或更高版本,或 Python Agent SDK v0.2.139 或更高版本elicitation_complete和elicitation_response用于用户提示引导流程
idle_prompt、auth_success 和 elicitation_dialog。
每个通知包括一个带有人类可读描述的 message 字段,以及可选的 title。
此示例将每个通知转发到 Slack 频道。它需要一个 Slack 传入 webhook URL,您可以通过将应用添加到您的 Slack 工作区并启用传入 webhooks 来创建:
Notification 事件触发时,hook 将通知的 message 发送到您的 webhook 目标的频道,前缀为 Agent status:。
修复常见问题
Hook 未触发
- 验证 hook 事件名称正确且区分大小写(
PreToolUse,而不是preToolUse) - 检查您的匹配器模式是否与工具名称完全匹配
- 确保 hook 在
options.hooks中的正确事件类型下 - 对于支持匹配器的非工具 hook,如
Notification和SubagentStop,匹配器匹配不同的字段,而Stop完全忽略匹配器(请参阅匹配器模式) - 当 Agent 达到
max_turns限制时,hook 可能不会触发,因为会话在 hook 可以执行前结束
匹配器未按预期过滤
匹配器仅匹配工具名称,而不是文件路径或其他参数。要按文件路径过滤,请在您的 hook 内检查tool_input.file_path:
Hook 超时
Claude Code 运行每个回调时都有超时限制,您可以在其HookMatcher 上使用 timeout 字段(以秒为单位)设置。当您未设置时,Claude Code 使用事件的默认值:大多数事件为 600 秒,UserPromptSubmit、PreModelSwitch 和 PostModelSwitch 为 30 秒,MessageDisplay 为 10 秒。Claude Code 在关闭期间运行 SessionEnd 回调时使用较短的 SessionEnd 超时预算,默认为 1.5 秒。
当回调超过其超时时间时,Claude Code 会取消它并丢弃其输出,会话继续而不是挂起。接下来发生的情况取决于事件:
PreToolUse: Claude Code 阻止该工具调用。作为替代,Claude 收到一个工具结果,说明 hook 未在超时前响应,轮次继续。在 v2.1.210 之前,Claude Code 将超时报告给 Claude 作为用户拒绝,这使得无人值守会话停止并等待输入。PostToolUse和PostToolUseFailure:Claude Code 保留工具结果,轮次继续。UserPromptSubmit和UserPromptExpansion:Claude Code 使用指明 hook 和超时的消息阻止该提示词,会话继续。在 v2.1.208 之前,当这些事件上的回调超时时,Claude Code 以error_during_execution结束查询。Stop和SubagentStop:超时的回调计为不返回任何决定。Agent 或子代理停止,就像该回调已允许它一样,您在该事件上的其他 hook 的决定仍然适用。在 Claude Code v2.1.273 之前,超时的Stop或SubagentStop回调计为失败的 hook 运行,Claude Code 丢弃了您在该事件上的其他 hook 的决定。SessionStart:超时的回调计为不返回任何输出,会话继续使用您的其他SessionStarthook 的输出。PreModelSwitch:Claude Code 阻止模型切换。未回答的 hook 尚未批准切换。- 其他事件,如
Notification、PreCompact和PostModelSwitch:Claude Code 记录失败并继续。
Stop 或 SessionStart 回调第一次超时时,Claude Code 还会向消息流添加一个 SDKInformationalMessage,说明驱动会话的应用未响应。当您的应用保持无响应时,后续超时不会重复该消息。
如果您在回调待处理时中断查询,Claude Code 会取消待处理的工具调用。在 v2.1.208 之前,如果您在待处理的 PreToolUse 回调期间中断,工具调用仍可能继续。
如果您的回调需要更多时间,请在其 HookMatcher 上设置更高的 timeout。在 TypeScript 中,使用第三个回调参数中的 AbortSignal 来在超时触发时优雅地处理取消。
工具意外被阻止
- 检查所有
PreToolUsehook 是否返回permissionDecision: 'deny' - 向您的 hook 添加日志记录以查看它们返回的
permissionDecisionReason - 验证匹配器模式不会太宽泛:空匹配器匹配所有工具
修改的输入未应用
-
确保
updatedInput在hookSpecificOutput内,而不是在顶级: -
不要将
updatedInput与permissionDecision: 'defer'配对,这会丢弃修改的输入。省略permissionDecision是可以的:修改的输入仍然通过正常权限评估应用。您也可以返回'allow'来自动批准修改的输入或'ask'来向用户显示以供批准 -
在
hookSpecificOutput中包括hookEventName以识别输出针对的 hook 类型
Python 中不可用会话 hook
SessionStart 和 SessionEnd 可以在 TypeScript 中注册为 SDK 回调 hook,但在 Python SDK 中不可用,因为其 HookEvent 类型省略了它们。在 Python 中,它们仅作为在设置文件(例如 .claude/settings.json)中定义的 shell 命令 hook 可用。您的 SDK 应用程序加载哪些设置文件取决于 setting_sources 或 settingSources。如果您设置了该选项,请包括包含这些 hook 的设置源:
client.receive_response() 的第一条消息作为您的触发器。
子代理权限提示倍增
生成多个子代理时,每个子代理可能会单独请求其自身工具调用的权限。要避免重复提示,请使用PreToolUse hook 自动批准特定工具,或配置权限规则,子代理从父对话继承这些规则。
子代理的递归 hook 循环
生成子代理的UserPromptSubmit hook 如果这些子代理触发相同的 hook,可能会创建无限循环。要防止这种情况:
- 使用共享变量或会话状态来跟踪您是否已在子代理内
- 将 hook 限定为仅对顶级 Agent 会话运行
systemMessage 未出现在输出中
systemMessage 字段向用户显示消息,而不是模型。在 Claude Code v2.1.227 或更高版本上,hook 的 systemMessage 可以在消息流中显示为 SDKInformationalMessage。它是否显示取决于事件。hooks 页面上每个事件的部分说明输出如何显示。要改为将上下文传递给模型,请返回 additionalContext。
在 v2.1.227 之前,SDK 仅在消息流中为 SessionStart 和 Setup hook 显示 hook 输出。对于任何其他事件,输出仅出现在 includeHookEvents(Python 中为 include_hook_events)添加的生命周期事件中。该选项的条目涵盖每个 hook 事件产生的生命周期事件。
如果您需要可靠地将 hook 决定呈现给您的应用程序,请单独记录它们或使用专用输出通道。
相关资源
- Claude Code hooks 参考:完整的 JSON 输入/输出架构、事件文档和匹配器模式
- Claude Code hooks 指南:shell 命令 hook 示例和演练
- TypeScript SDK 参考:hook 类型、输入/输出定义和配置选项
- Python SDK 参考:hook 类型、输入/输出定义和配置选项
- 权限:控制您的代理可以做什么
- 自定义工具:构建工具以扩展代理功能