Hook 生命周期
Hooks 在 Claude Code 会话期间的特定点触发。当事件触发且匹配器匹配时,Claude Code 会将关于该事件的 JSON 上下文传递给您的 hook 处理程序。对于命令 hooks,输入通过 stdin 到达。对于 HTTP hooks,它作为 POST 请求体到达。您的处理程序随后可以检查输入、采取行动并可选地返回决定。 事件分为三种频率:- 每个会话一次:
SessionStart和SessionEnd - 每轮一次:
UserPromptSubmit、Stop和StopFailure - 代理循环内的每个工具调用:
PreToolUse和PostToolUse
Hook 如何解析
要了解这些部分如何组合在一起,请考虑这个PreToolUse hook,它阻止破坏性 shell 命令。matcher 缩小到 Bash 工具调用,if 条件进一步缩小到匹配 rm * 的 Bash 子命令,因此 block-rm.sh 仅在两个过滤器都匹配时生成:
rm -rf,则返回 permissionDecision 为 "deny":
Bash "rm -rf /tmp/build"。以下是发生的情况:
1
事件触发
PreToolUse 事件触发。Claude Code 将工具输入作为 JSON 通过 stdin 发送到 hook:2
匹配器检查
匹配器
"Bash" 与工具名称匹配,因此此 hook 组激活。如果您省略匹配器或使用 "*",该组在事件的每次出现时激活。3
If 条件检查
if 条件 "Bash(rm *)" 匹配,因为 rm -rf /tmp/build 是匹配 rm * 的子命令,因此此处理程序生成。如果命令是 npm test,if 检查会失败,block-rm.sh 永远不会运行,避免进程生成开销。if 字段是可选的;没有它,匹配组中的每个处理程序都运行。4
Hook 处理程序运行
脚本检查完整命令并找到 如果命令是更安全的
rm -rf,因此它将决定打印到 stdout:rm 变体,如 rm file.txt,脚本会改为执行 exit 0。退出代码 0 且无输出意味着 hook 没有决定要报告,因此工具调用继续通过正常的权限流程。hook 可以拒绝调用,但保持沉默不会批准它。5
Claude Code 对结果采取行动
Claude Code 读取 JSON 决定,阻止工具调用,并向 Claude 显示原因。
配置
Hooks 在 JSON 设置文件中定义。配置有三个嵌套级别: 有关完整的演练和带注释的示例,请参阅上面的Hook 如何解析。此页面为每个级别使用特定术语:hook 事件表示生命周期点,匹配器组表示过滤器,hook 处理程序表示运行的 shell 命令、HTTP 端点、MCP 工具、提示或代理。“Hook”本身指的是一般功能。
Hook 位置
您定义 hook 的位置决定了其范围:
有关设置文件解析的详细信息,请参阅设置。企业管理员可以使用
allowManagedHooksOnly 来阻止用户、项目和插件 hooks。在托管设置 enabledPlugins 中强制启用的插件中的 Hooks 是豁免的,因此管理员可以通过组织市场分发经过审查的 hooks。请参阅Hook 配置。
匹配器模式
matcher 字段过滤 hooks 何时触发。匹配器的评估方式取决于它包含的字符:
在正则表达式路径上的匹配器使用 JavaScript 的
RegExp.prototype.test 进行测试,该测试在值中任何位置的匹配时成功。Edit.* 匹配 Edit 和 NotebookEdit;当您需要整个字符串匹配时,用 ^ 和 $ 包装模式,如 ^Edit$。
逗号分隔符和周围空格容差需要 Claude Code v2.1.191 或更高版本。
精确匹配集中的连字符需要 Claude Code v2.1.195 或更高版本。在早期版本中,像 code-reviewer 这样的连字符名称被评估为未锚定的正则表达式,因此它也会对 senior-code-reviewer 触发;在这些版本上将其锚定为 ^code-reviewer$ 以仅匹配该名称。
FileChanged 和 StopFailure 使用更窄的精确匹配集,仅包含字母、数字、_ 和 |。这两个事件的匹配器中的连字符、空格或逗号将其保留在正则表达式路径上,仅 | 分隔替代项。下表中列出的支持匹配器的所有其他事件接受 | 或 ,。
FileChanged 事件在构建其监视列表时不遵循这些规则。请参阅 FileChanged。
每个事件类型在不同的字段上匹配:
匹配器针对 Claude Code 在 stdin 上发送给您的 hook 的JSON 输入中的字段运行。对于工具事件,该字段是
tool_name。每个hook 事件部分列出了完整的匹配器值集和该事件的输入架构。
此示例仅在 Claude 写入或编辑文件时运行 linting 脚本:
UserPromptSubmit、PostToolBatch、Stop、TeammateIdle、TaskCreated、TaskCompleted、WorktreeCreate、WorktreeRemove、MessageDisplay 和 CwdChanged 不支持匹配器,总是在每次出现时触发。如果您向这些事件添加 matcher 字段,它会被静默忽略。
对于工具事件,您可以通过在单个 hook 处理程序上设置if 字段来更狭隘地过滤。if 使用权限规则语法来匹配工具名称和参数,因此 "Bash(git *)" 仅在任何 Bash 输入的子命令与 git * 匹配时运行,"Edit(*.ts)" 仅对 TypeScript 文件运行。
匹配 MCP 工具
MCP 服务器工具在工具事件中显示为常规工具(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied),因此您可以像匹配任何其他工具名称一样匹配它们。
MCP 工具遵循命名模式 mcp__<server>__<tool>,例如:
mcp__memory__create_entities:Memory 服务器的创建实体工具mcp__filesystem__read_file:Filesystem 服务器的读取文件工具mcp__github__search_repositories:GitHub 服务器的搜索工具
.*。.* 是必需的:像 mcp__memory 或 mcp__brave-search 这样的匹配器仅包含精确匹配字符,因此它作为精确字符串进行比较,不匹配任何工具。
mcp__memory__.*匹配来自memory服务器的所有工具mcp__brave-search__.*匹配来自名称包含连字符的服务器的所有工具mcp__.*__write.*匹配来自任何服务器的任何名称以write开头的工具
mcp__brave-search 这样的裸连字符前缀被评估为未锚定的正则表达式,并匹配来自该服务器的每个工具。mcp__brave-search__.* 形式在每个版本上都有效。
来自插件捆绑的 MCP 服务器的工具使用包含插件名称的作用域服务器段:mcp__plugin_<plugin-name>_<server-name>__<tool>。针对裸服务器密钥编写的匹配器永远不会对这些工具触发。对于在密钥 db 下捆绑服务器的名为 my-plugin 的插件,query 工具显示为 mcp__plugin_my-plugin_db__query,因此来自该服务器的每个工具的匹配器是 mcp__plugin_my-plugin_db__.*。在处理程序的if 字段中使用相同的作用域工具名称。有关如何构建作用域名称的信息,请参阅插件提供的 MCP 服务器。
此示例记录所有内存服务器操作并验证来自任何 MCP 服务器的写入操作:
Hook 处理程序字段
内部hooks 数组中的每个对象都是一个 hook 处理程序:当匹配器匹配时运行的 shell 命令、HTTP 端点、MCP 工具、LLM 提示或代理。有五种类型:
- 命令 hooks(
type: "command"):运行 shell 命令。您的脚本在 stdin 上接收事件的JSON 输入,并通过退出代码和 stdout 传回结果。 - HTTP hooks(
type: "http"):将事件的 JSON 输入作为 HTTP POST 请求发送到 URL。端点通过使用与命令 hooks 相同的JSON 输出格式的响应体传回结果。 - MCP 工具 hooks(
type: "mcp_tool"):在已连接的MCP 服务器上调用工具。工具的文本输出被视为命令 hook stdout。 - 提示 hooks(
type: "prompt"):向 Claude 模型发送提示以进行单轮评估。模型返回 yes/no 决定作为 JSON。请参阅基于提示的 hooks。 - 代理 hooks(
type: "agent"):生成一个可以使用 Read、Grep 和 Glob 等工具来验证条件的 subagent,然后返回决定。代理 hooks 是实验性的,可能会改变。请参阅基于代理的 hooks。
args 去重,HTTP hooks 按 URL 去重。
处理程序在当前目录中运行,使用 Claude Code 的环境。在远程 web 环境中,$CLAUDE_CODE_REMOTE 环境变量设置为 "true",在本地 CLI 中未设置。从 v2.1.199 开始,$CLAUDE_CODE_BRIDGE_SESSION_ID设置为远程控制会话 ID,而本地会话具有活跃的远程控制连接。
通用字段
这些字段适用于所有 hook 类型:if 字段恰好包含一个权限规则。没有 &&、|| 或列表语法来组合规则;要应用多个条件,请为每个条件定义一个单独的 hook 处理程序。
对于 Bash 模式,您的 hook 命令是否运行取决于模式的形状和 Claude 调用的 Bash 命令。前导 VAR=value 赋值在匹配前被剥离。
过滤器也会失败开放,当 Bash 命令无法解析时无论如何运行您的 hook。因为
if 过滤器是尽力而为的,使用权限系统而不是 hook 来强制执行硬允许或拒绝。
命令 hook 字段
除了通用字段外,命令 hooks 还接受这些字段:
当设置
args 时,命令 hook 以 exec 形式运行,当省略 args 时以 shell 形式运行。每当 hook 引用路径占位符时设置 args,因为每个元素作为一个参数传递,不带引号。当您需要 shell 功能(如管道或 &&)时,或当两个问题都不适用时,省略 args。
Exec 形式在存在 args 时运行。Claude Code 在 PATH 上解析 command 作为可执行文件,并直接使用 args 作为参数向量生成它。没有 shell,因此每个 args 元素恰好是一个参数,完全按照编写的方式,路径占位符如 ${CLAUDE_PLUGIN_ROOT} 被替换为 command 和每个 args 元素中的纯字符串。特殊字符如撇号、$ 和反引号逐字通过,因为没有 shell 来解释它们。在任何平台上都不会发生 shell 标记化。
Shell 形式在省略 args 时运行。command 字符串被传递给 shell:在 macOS 和 Linux 上为 sh -c,在 Windows 上为 Git Bash,或在未安装 Git Bash 时为 PowerShell。设置 shell 字段以显式选择。shell 标记化字符串,展开变量,并解释管道、&&、重定向和 glob。
在 Windows 上,exec 形式需要
command 解析为真实可执行文件,如 .exe。npm、npx、eslint 和其他工具在 node_modules/.bin 中安装的 .cmd 和 .bat 垫片不是可执行文件,不能在没有 shell 的情况下生成。要在 exec 形式中运行它们,直接使用 node 调用底层脚本,例如 "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/node_modules/eslint/bin/eslint.js"]。node 加脚本路径模式在每个平台上都有效,因为 node.exe 是真实二进制文件。要按名称运行 .cmd 或 .bat 垫片,请使用 shell 形式。CLAUDE_PROJECT_DIR、CLAUDE_PLUGIN_ROOT 和 CLAUDE_PLUGIN_DATA 导出到生成的进程上,因此脚本可以读取 process.env.CLAUDE_PLUGIN_ROOT,无论它是如何启动的。
插件 hooks 另外替换 ${user_config.*} 值,仅在 exec 形式中:该值被替换为 command 和每个 args 元素中的纯字符串,因此没有 shell 重新解析它。
一个 shell 形式的插件 hook,其 command 引用 ${user_config.*} 会失败并出现错误,而不是运行。要从 shell 形式的 hook 使用选项值,请读取 $CLAUDE_PLUGIN_OPTION_<KEY> 环境变量,例如 webhook_url 选项的 $CLAUDE_PLUGIN_OPTION_WEBHOOK_URL,或设置 args 以将 hook 切换到 exec 形式。在 v2.1.207 之前,shell 形式的插件 hook 命令也替换了 ${user_config.*}。
在 exec 形式中,
command 仅是可执行文件名或路径。如果 command 是没有路径分隔符的裸名称,并且与 args 一起包含空格,Claude Code 会记录警告,因为生成会失败:没有名为 node script.js 的可执行文件。将额外的令牌移到 args 中。包含空格的绝对路径,如 C:\Program Files\nodejs\node.exe,是单个有效的可执行文件,不会触发警告。HTTP hook 字段
除了通用字段外,HTTP hooks 还接受这些字段:
Claude Code 使用
Content-Type: application/json 将 hook 的JSON 输入作为 POST 请求体发送。响应体使用与命令 hooks 相同的JSON 输出格式。
错误处理与命令 hooks 不同:非 2xx 响应、连接失败和超时都会产生非阻止错误,允许执行继续。要阻止工具调用或拒绝权限,返回 2xx 响应,其 JSON 体包含 decision: "block" 或 hookSpecificOutput 与 permissionDecision: "deny"。
此示例将 PreToolUse 事件发送到本地验证服务,使用来自 MY_TOKEN 环境变量的令牌进行身份验证:
MCP 工具 hook 字段
除了通用字段外,MCP 工具 hooks 还接受这些字段:
工具的文本内容被视为命令 hook stdout:如果它解析为有效的JSON 输出,则作为决定进行处理,否则显示为纯文本。如果命名的服务器未连接,或工具返回
isError: true,hook 会产生非阻止错误,执行继续。
MCP 工具 hooks 在 Claude Code 连接到您的 MCP 服务器后在每个 hook 事件上可用。SessionStart 和 Setup 通常在服务器完成连接之前触发,因此这些事件上的 hooks 应该期望在首次运行时出现”未连接”错误。
此示例在每个 Write 或 Edit 后在 my_server MCP 服务器上调用 security_scan 工具,传递编辑文件的路径:
提示和代理 hook 字段
除了通用字段外,提示和代理 hooks 还接受这些字段:按路径引用脚本
使用这些占位符按项目或插件根目录引用 hook 脚本,无论 hook 运行时的工作目录如何:${CLAUDE_PROJECT_DIR}:项目根目录。Claude Code 也在stdio MCP 服务器和插件 LSP 服务器的环境中设置此变量。${CLAUDE_PLUGIN_ROOT}:插件的安装目录,用于与插件捆绑的脚本。在每次插件更新时更改。${CLAUDE_PLUGIN_DATA}:插件的持久数据目录,用于应该在插件更新后保留的依赖项和状态。
args 元素作为一个参数传递,不带 shell 标记化,因此包含空格或特殊字符的路径不需要引号。在 shell 形式中,用双引号包装每个占位符。
- 项目脚本
- 插件脚本
此示例使用
${CLAUDE_PROJECT_DIR} 在任何 Write 或 Edit 工具调用后从项目的 .claude/hooks/ 目录运行样式检查器:Skills 和代理中的 Hooks
除了设置文件和插件外,hooks 还可以使用 frontmatter 直接在skills和subagents中定义。这些 hooks 的范围限于组件的生命周期,仅在该组件活跃时运行。 支持所有 hook 事件。对于 subagents,Stop hooks 会自动转换为 SubagentStop,因为这是 subagent 完成时触发的事件。
Hooks 使用与基于设置的 hooks 相同的配置格式,但范围限于组件的生命周期,并在其完成时清理。
此 skill 定义了一个 PreToolUse hook,在每个 Bash 命令之前运行安全验证脚本:
/hooks 菜单
在 Claude Code 中键入 /hooks 以打开您配置的 hooks 的只读浏览器。菜单显示每个 hook 事件及其配置的 hooks 计数,让您深入了解匹配器,并显示每个 hook 处理程序的完整详细信息。使用它来验证配置、检查 hook 来自哪个设置文件,或检查 hook 的命令、提示或 URL。
菜单显示所有五种 hook 类型:command、prompt、agent、http 和 mcp_tool。每个 hook 都标有 [type] 前缀和指示其定义位置的源:
User:来自~/.claude/settings.jsonProject:来自.claude/settings.jsonLocal:来自.claude/settings.local.jsonPlugin:来自插件的hooks/hooks.jsonSession:在当前会话中在内存中注册Built-in:由 Claude Code 内部注册
禁用或移除 hooks
要移除 hook,请从设置 JSON 文件中删除其条目。 要临时禁用所有 hooks 而不移除它们,请在设置文件中设置"disableAllHooks": true。没有办法在保持 hook 在配置中的同时禁用单个 hook。
disableAllHooks 设置遵守托管设置层次结构。如果管理员通过托管策略设置配置了 hooks,则在用户、项目或本地设置中设置的 disableAllHooks 无法禁用这些托管 hooks。仅在托管设置级别设置的 disableAllHooks 可以禁用托管 hooks。
对设置文件中 hooks 的直接编辑通常由文件监视程序自动拾取。
Hook 输入和输出
命令 hooks 通过 stdin 接收 JSON 数据,并通过退出代码、stdout 和 stderr 传回结果。HTTP hooks 接收相同的 JSON 作为 POST 请求体,并通过 HTTP 响应体传回结果。本部分涵盖所有事件通用的字段和行为。每个事件在Hook 事件下的部分包括其特定的输入架构和决定控制选项。 从 v2.1.139 开始,在 macOS 和 Linux 上,命令 hooks 在没有控制终端的自己的会话中运行。hook 进程和任何子进程无法打开/dev/tty 或直接向 Claude Code 界面发送转义序列。Windows 没有 /dev/tty。要在任何平台上向用户显示消息,请在 JSON 输出中返回systemMessage。要触发桌面通知、设置窗口标题或响铃,请改为返回terminalSequence。
通用输入字段
Hook 事件接收这些字段作为 JSON,除了每个hook 事件部分中记录的事件特定字段。对于命令 hooks,此 JSON 通过 stdin 到达。对于 HTTP hooks,它作为 POST 请求体到达。
使用
--agent 运行或在 subagent 内部时,包括两个额外字段:
仅
SessionStart hooks 可以接收 model 字段,且不保证存在。没有 $CLAUDE_MODEL 环境变量。Hook 进程继承父环境,因此如果您在 shell 中设置了 $ANTHROPIC_MODEL,它可以读取该值,但当您在会话期间使用 /model 切换模型时,该值不会改变。一组变量不被继承:Claude Code 从它生成的每个子进程中删除 OTEL_* 导出器变量,包括 hooks。
例如,Bash 命令的 PreToolUse hook 在 stdin 上接收:
tool_name 和 tool_input 字段是事件特定的。每个hook 事件部分记录了该事件的额外字段。
退出代码输出
您的 hook 命令的退出代码告诉 Claude Code 操作是否应该继续、被阻止或被忽略。 退出 0 表示成功。Claude Code 解析 stdout 以获取JSON 输出字段。JSON 输出仅在退出 0 时处理。对于大多数事件,stdout 被写入调试日志,但不显示在成绩单中。例外是UserPromptSubmit、UserPromptExpansion 和 SessionStart,其中 stdout 作为 Claude 可以看到和作用的上下文添加。
退出 2 表示阻止错误。Claude Code 忽略 stdout 和其中的任何 JSON。相反,stderr 文本被反馈给 Claude 作为错误消息。效果取决于事件:PreToolUse 阻止工具调用,UserPromptSubmit 拒绝提示,等等。有关完整列表,请参阅每个事件的退出代码 2 行为。
任何其他退出代码 是大多数 hook 事件的非阻止错误。成绩单显示 <hook name> hook error 通知,然后是 stderr 的第一行,因此您可以在不使用 --debug 的情况下识别原因。执行继续,完整的 stderr 被写入调试日志。
例如,一个 hook 命令脚本,阻止危险的 Bash 命令:
每个事件的退出代码 2 行为
退出代码 2 是 hook 发出”停止,不要这样做”的方式。效果取决于事件,因为某些事件代表可以被阻止的操作(如尚未发生的工具调用),而其他事件代表已经发生或无法防止的事情。
对于
SessionStart、Setup 和 SubagentStart,退出代码 2 stderr 在成绩单中呈现为 <hook name> hook error 通知,与非阻止错误的方式相同。Claude 看不到它,会话或 subagent 继续进行。对于 SubagentStart,通知出现在 subagent 自己的成绩单中,而不是在父对话中。
从 Claude Code v2.1.199 开始,SessionStart、Setup 和 SubagentStart 在成绩单中显示退出代码 2 stderr。早期版本仅将其写入调试日志。
HTTP 响应处理
HTTP hooks 使用 HTTP 状态代码和响应体而不是退出代码和 stdout:- 2xx 带空体:成功,等同于退出代码 0 且无输出
- 2xx 带纯文本体:成功,文本作为上下文添加
- 2xx 带 JSON 体:成功,使用与命令 hooks 相同的JSON 输出架构解析
- 非 2xx 状态:非阻止错误,执行继续
- 连接失败或超时:非阻止错误,执行继续
JSON 输出
退出代码让您允许或阻止,但 JSON 输出提供更细粒度的控制。与其使用代码 2 退出来阻止,不如退出 0 并将 JSON 对象打印到 stdout。Claude Code 从该 JSON 读取特定字段以控制行为,包括决定控制以阻止、允许或升级给用户。您必须为每个 hook 选择一种方法,而不是两种:要么单独使用退出代码进行信号传递,要么退出 0 并打印 JSON 以进行结构化控制。Claude Code 仅在退出 0 时处理 JSON。如果您退出 2,任何 JSON 都会被忽略。
additionalContext、systemMessage 和纯 stdout,上限为 10,000 个字符。超过此限制的输出被保存到文件并替换为预览和文件路径,与大型工具结果的处理方式相同。
JSON 对象支持三种字段:
- 通用字段,如
continue,在所有事件中工作。这些列在下表中。 - 顶级
decision和reason由某些事件用于阻止或提供反馈。 hookSpecificOutput是一个嵌套对象,用于需要更丰富控制的事件。它需要一个设置为事件名称的hookEventName字段。
要无论事件类型如何都完全停止 Claude:
发出终端通知
terminalSequence 字段需要 Claude Code v2.1.141 或更高版本。
Hooks 运行时没有控制终端,因此直接向 /dev/tty 写入转义序列会失败。相反,在 terminalSequence 字段中返回转义序列,Claude Code 通过其自己的终端写入路径为您发出它。这是无竞争的,在 tmux 和 GNU screen 内工作,并在 Windows 上工作,其中没有 /dev/tty。
该字段接受一个或多个允许列表转义序列的字符串:
- OSC
0、1、2:窗口和图标标题 - OSC
9:iTerm2、ConEmu、Windows Terminal 和 WezTerm 通知,包括9;4任务栏进度 - OSC
99:Kitty 通知 - OSC
777:urxvt、Ghostty 和 Warp 通知 - 裸 BEL
Notification hook 触发桌面通知。转义序列使用 printf 八进制转义构建,因此控制字节永远不会出现在 shell 命令行上,jq -n --arg 构建 JSON 输出,因此通知消息中的引号、反斜杠和换行符被正确转义:
{ "terminalSequence": "..." } 形状从任何 shell 或语言都相同。在 Windows 上,在 PowerShell 或脚本中构建转义字符串并发出相同的 JSON 对象。
terminalSequence 是之前直接向 /dev/tty 写入转义序列的 hooks 的受支持替代品。允许列表限制为无法移动光标或改变颜色的序列,因此 hook 永远无法破坏屏幕上的提示。为 Claude 添加上下文
additionalContext 字段将来自您的 hook 的字符串传递到 Claude 的上下文窗口中。Claude Code 将字符串包装在系统提醒中,并将其插入到 hook 触发的对话点。Claude 在下一个模型请求时读取提醒,但它不会在界面中显示为聊天消息。
在 hookSpecificOutput 中返回 additionalContext 以及事件名称:
- SessionStart、Setup 和 SubagentStart:在对话开始,在第一个提示之前
- UserPromptSubmit 和 UserPromptExpansion:与提交的提示一起
- PreToolUse、PostToolUse、PostToolUseFailure 和 PostToolBatch:在工具结果旁边
- Stop 和 SubagentStop:在轮次末尾。对话继续,以便 Claude 可以对反馈采取行动。请参阅Stop 决定控制
additionalContext 时,Claude 接收所有值。如果值超过 10,000 个字符,Claude Code 将完整文本写入会话目录中的文件,并将 Claude 传递文件路径以及简短预览。
使用 additionalContext 来获取 Claude 应该了解的有关您的环境当前状态或刚刚运行的操作的信息:
- 环境状态:当前分支、部署目标或活跃的功能标志
- 条件项目规则:哪个测试命令适用于刚刚编辑的文件,哪些目录在此 worktree 中是只读的
- 外部数据:分配给您的开放问题、最近的 CI 结果、从内部服务获取的内容
bun test”读作项目信息。框架为带外系统命令的文本可能会触发 Claude 的提示注入防御,这会导致 Claude 将文本呈现给您,而不是将其视为上下文。
一旦注入,文本就会保存在会话成绩单中。对于 PostToolUse 或 UserPromptSubmit 等中期事件,使用 --continue 或 --resume 恢复会重放保存的文本,而不是为过去的轮次重新运行 hook,因此时间戳或提交 SHA 等值在恢复时变得陈旧。SessionStart hooks 在使用 source 设置为 "resume" 的 --resume 恢复时再次运行,因此它们可以刷新其上下文。
决定控制
并非每个事件都支持通过 JSON 阻止或控制行为。支持的事件各自使用不同的字段集来表达该决定。在编写 hook 之前,使用此表作为快速参考:
一些事件也可以重写内容而不仅仅允许或阻止它:
PreToolUse:updatedInput直接在hookSpecificOutput下替换工具的参数,然后它运行。请参阅PreToolUse 决定控制PermissionRequest:updatedInput在decision对象内。请参阅PermissionRequest 决定控制PostToolUse:updatedToolOutput替换工具的结果。请参阅PostToolUse 决定控制UserPromptSubmit:无法替换提示;仅在其旁边注入additionalContext
PreToolUse 处拦截出站工具输入,在 PostToolUse 处拦截入站工具结果。
以下是每种模式的实际示例:
- 顶级决定
- PreToolUse
- PermissionRequest
由
UserPromptSubmit、UserPromptExpansion、PostToolUse、PostToolUseFailure、PostToolBatch、Stop、SubagentStop、ConfigChange 和 PreCompact 使用。唯一的值是 "block"。要允许操作继续,从您的 JSON 中省略 decision,或退出 0 而不带任何 JSON:Hook 事件
每个事件对应于 Claude Code 生命周期中 hooks 可以运行的一个点。下面的部分按照生命周期排序:从会话设置通过代理循环到会话结束。每个部分描述事件何时触发、它支持的匹配器、它接收的 JSON 输入以及如何通过输出控制行为。SessionStart
在 Claude Code 启动新会话或恢复现有会话时运行。用于加载开发上下文,如现有问题或代码库的最近更改,或设置环境变量。对于不需要脚本的静态上下文,请改用CLAUDE.md。 SessionStart 在每个会话上运行,因此保持这些 hooks 快速。仅支持type: "command" 和 type: "mcp_tool" hooks。
匹配器值对应于会话的启动方式:
SessionStart 输入
除了通用输入字段外,SessionStart hooks 还接收source 和可选的 model、agent_type 和 session_title:
SessionStart 决定控制
您的 hook 脚本打印到 stdout 的任何文本都作为 Claude 的上下文添加。除了所有 hooks 可用的JSON 输出字段外,您还可以返回这些事件特定字段:suppressOutput 或 sessionTitle)结合时,使用 JSON 形式。
当 SessionStart hook 安装或更新 skills 时使用 reloadSkills。Skill 发现通常在 SessionStart hooks 完成之前运行,因此 hook 写入 ~/.claude/skills/ 或 .claude/skills/ 的文件否则只会在下一个会话中出现。此示例同步共享 skills 仓库并请求重新扫描:
持久化环境变量
SessionStart hooks 可以访问CLAUDE_ENV_FILE 环境变量,该变量提供一个文件路径,您可以在其中为后续 Bash 命令持久化环境变量。
要设置单个环境变量,请将 export 语句写入 CLAUDE_ENV_FILE。使用追加(>>)来保留由其他 hooks 设置的变量:
CLAUDE_ENV_FILE 可用于 SessionStart、Setup、CwdChanged 和 FileChanged hooks。其他 hook 类型无法访问此变量。Setup
仅当您使用--init-only 启动 Claude Code,或在非交互模式中使用 -p 标志与 --init 或 --maintenance 结合时触发。它不在正常启动时触发。使用它进行一次性依赖安装或您从 CI 或脚本显式触发的计划清理,与正常会话启动分开。对于每个会话的初始化,请改用SessionStart。
匹配器值对应于触发 hook 的 CLI 标志:
--init-only 运行 Setup hooks 和 SessionStart hooks(带 startup 匹配器),然后退出而不启动对话。--init 和 --maintenance 仅在与 -p 结合时触发 Setup hooks;在交互式会话中,这两个标志目前不触发 Setup hooks。
因为 Setup 不在每次启动时触发,需要安装依赖的插件不能仅依赖 Setup。实际的模式是在首次使用时检查依赖,如果缺失则安装,例如测试 ${CLAUDE_PLUGIN_DATA}/node_modules 的 hook 或 skill,如果不存在则运行 npm install。请参阅持久数据目录了解在何处存储已安装的依赖。
Setup 输入
除了通用输入字段外,Setup hooks 还接收一个trigger 字段,设置为 "init" 或 "maintenance":
Setup 决定控制
Setup hooks 无法阻止。任何非零退出代码(包括 2)都会向用户显示 stderr 作为<hook name> hook error 通知,执行继续。在非交互模式中,hook 输出仅在您使用 --verbose 启动时出现。
要将信息传入 Claude 的上下文,在 JSON 输出中返回 additionalContext;纯 stdout 仅写入调试日志。除了所有 hooks 可用的JSON 输出字段外,您还可以返回这些事件特定字段:
CLAUDE_ENV_FILE。写入该文件的变量持久化到会话的后续 Bash 命令中,就像在SessionStart hooks中一样。仅支持 type: "command" 和 type: "mcp_tool" hooks。
InstructionsLoaded
当CLAUDE.md 或 .claude/rules/*.md 文件加载到上下文中时触发。此事件在会话启动时为急切加载的文件触发,稍后当文件被懒加载时再次触发,例如当 Claude 访问包含嵌套 CLAUDE.md 的子目录或条件规则与 paths: frontmatter 匹配时。该 hook 不支持阻止或决定控制。它异步运行以用于可观测性目的。
匹配器针对 load_reason 运行。例如,使用 "matcher": "session_start" 仅对会话启动时加载的文件触发,或使用 "matcher": "path_glob_match|nested_traversal" 仅对懒加载触发。
InstructionsLoaded 输入
除了通用输入字段外,InstructionsLoaded hooks 还接收这些字段:InstructionsLoaded 决定控制
InstructionsLoaded hooks 没有决定控制。它们无法阻止或修改指令加载。使用此事件进行审计日志记录、合规性跟踪或可观测性。UserPromptSubmit
在用户提交提示时运行,在 Claude 处理之前。这允许您根据提示/对话添加额外上下文、验证提示或阻止某些类型的提示。UserPromptSubmit hooks 对 command、http 和 mcp_tool 类型的默认超时为 30 秒,比这些类型在其他事件上的 600 秒默认值更短。因为此 hook 在每个提示之前运行并阻止模型处理直到完成,卡住的 hook 会停滞会话。如果您的 hook 需要更多时间,在 hook 条目中设置 timeout 字段。
达到超时的 UserPromptSubmit hook 被取消,其输出(包括任何 additionalContext)被丢弃。提示仍然到达 Claude,但没有该上下文。从 v2.1.196 开始,成绩单显示一个通知,命名 hook、触发的超时以及输出被丢弃。早期版本取消 hook 而不显示通知。
在 UserPromptSubmit 上达到超时的Agent SDK 回调 hook会用命名 hook 和超时的消息阻止提示,因为那里的回调可能充当必须不失败打开的策略门。会话继续。在 v2.1.208 之前,该事件上的回调超时以执行错误结束轮次。
UserPromptSubmit 输入
除了通用输入字段外,UserPromptSubmit hooks 还接收包含用户提交的文本的prompt 字段。
UserPromptSubmit 决定控制
UserPromptSubmit hooks 可以控制用户提示是否被处理并添加上下文。所有JSON 输出字段都可用。
有两种方法可以在退出代码 0 时向对话添加上下文:
- 纯文本 stdout:写入 stdout 的任何非 JSON 文本都作为上下文添加
- 带
additionalContext的 JSON:使用下面的 JSON 格式以获得更多控制。additionalContext字段作为上下文添加
additionalContext 值作为系统提醒注入,Claude 读取而不显示成绩单条目。
要阻止提示,返回一个 JSON 对象,其中 decision 设置为 "block":
UserPromptExpansion
当用户输入的斜杠命令在到达 Claude 之前展开为提示时运行。使用此来阻止特定命令的直接调用、为特定 skill 注入上下文或记录用户调用哪些命令。例如,匹配deploy 的 hook 可以阻止 /deploy,除非存在批准文件,或匹配审查 skill 的 hook 可以将团队的审查清单附加为 additionalContext。
此事件涵盖 PreToolUse 不涵盖的路径:匹配 Skill 工具的 PreToolUse hook 仅在 Claude 调用工具时触发,但直接输入 /skillname 绕过 PreToolUse。UserPromptExpansion 在该直接路径上触发。
在 command_name 上匹配。留空匹配器以对每个提示类型斜杠命令触发。
UserPromptExpansion 输入
除了通用输入字段外,UserPromptExpansion hooks 还接收expansion_type、command_name、command_args、command_source 和原始 prompt 字符串。expansion_type 字段对于 skill 和自定义命令为 slash_command,或对于 MCP 服务器提示为 mcp_prompt。
UserPromptExpansion 决定控制
UserPromptExpansion hooks 可以阻止展开或添加上下文。所有JSON 输出字段都可用。
MessageDisplay
在助手消息流向屏幕时运行。Claude Code 分批显示消息:每次一批新完成的行准备好渲染时,hook 运行一次,包含这些行,Claude Code 在其位置渲染 hook 的替换文本。长消息产生多个调用;短消息可能只产生一个。 使用 MessageDisplay 来:- 剥离 markdown 以获得最小显示
- 转换 Agent SDK 应用向其用户显示的文本
- 从 Claude 的响应中编辑 API 密钥或内部主机名
timeout 字段。
MessageDisplay 仅用于显示:替换文本仅改变屏幕上呈现的内容。成绩单和 Claude 看到的内容保持原始文本,因此 Claude 永远看不到替换,详细模式显示原始内容。Hook 仅接收助手消息文本,因此工具结果和您输入的文本呈现不变。
MessageDisplay 不支持匹配器,对每个流向文本的助手消息触发;没有文本的消息(如仅工具调用响应)不触发它。
在非交互式运行中,包括 Agent SDK 查询和 claude -p,MessageDisplay 每个助手消息运行一次,而不是每批行运行一次。单个调用在消息完成后到达,并携带完整消息文本:index 为 0,final 为 true,delta 保存整个消息。为每个消息收集 delta 文本的 hook 在两种模式中接收相同的总文本。
MessageDisplay 输入
除了通用输入字段外,MessageDisplay hooks 还接收轮次和消息的标识符、此调用在消息中的位置以及delta 中的新文本。批次边界取决于文本如何流动,因此使用 index 和 final 来跟踪通过消息的进度,而不是期望行以特定方式分组。
MessageDisplay 输出
除了所有 hooks 可用的JSON 输出字段外,MessageDisplay hooks 可以返回displayContent 来替换屏幕上的 delta:
MessageDisplay hooks 没有决定控制。它们无法阻止消息或改变成绩单中存储或发送给 Claude 的内容。
此示例从 Claude 的响应中剥离 markdown 格式以获得纯文本显示。脚本从 stdin 读取每个批次,从
delta 中移除粗体标记和内联代码反引号,并将结果作为 displayContent 返回。
- macOS/Linux
- Windows (PowerShell)
在您的设置文件中为事件注册命令 hook:将此脚本保存到您的项目中的 脚本需要
.claude/hooks/plain-display.sh 并使用 chmod +x 使其可执行:jq 在您的 PATH 上。jq 缺失,Claude Code 显示原始文本并仅在调试输出中注意失败,而不是在会话中。
PreToolUse
在 Claude 创建工具参数后和处理工具调用之前运行。在工具名称上匹配:Bash、Edit、Write、Read、Glob、Grep、Agent、WebFetch、WebSearch、AskUserQuestion、ExitPlanMode 和任何MCP 工具名称。
使用PreToolUse 决定控制来允许、拒绝、询问或延迟工具调用。
PreToolUse 输入
除了通用输入字段外,PreToolUse hooks 还接收tool_name、tool_input 和 tool_use_id。tool_input 字段取决于工具:
执行 shell 命令。
创建或覆盖文件。
替换现有文件中的字符串。
读取文件内容。
查找与 glob 模式匹配的文件。
使用正则表达式搜索文件内容。
获取和处理 web 内容。
搜索网络。
生成一个subagent。
在
PostToolUse 中,已完成的 Agent 调用的 tool_response 携带 subagent 的最终文本以及使用遥测。读取这些字段以从 hook 记录每个 subagent 的成本:
对于后台 subagents,工具在启动 subagent 后立即返回,因此
tool_response 不携带使用字段。它具有 status: "async_launched"、agentId、description、prompt、outputFile 和 resolvedModel。
resolvedModel 字段命名 subagent 实际运行的模型,可能与 tool_input 中的 model 值不同。它需要 Claude Code v2.1.174 或更高版本。
向用户提出一到四个多选题。
呈现一个计划并要求用户在 Claude 离开Plan Mode之前批准它。Claude 在调用工具之前将计划写入磁盘上的文件,因此来自模型的字面
tool_input 通常为空。Claude Code 在将输入传递给 hooks 之前注入计划内容和文件路径。
在
PostToolUse 中,tool_response 是一个对象,具有 plan 和 filePath 字段,保存批准的计划,加上内部状态标志。读取 tool_response.plan 以获取计划内容,而不是从磁盘重新读取文件。
PreToolUse 决定控制
PreToolUse hooks 可以控制工具调用是否继续。与使用顶级 decision 字段的其他 hooks 不同,PreToolUse 在 hookSpecificOutput 对象内返回其决定。这给了它更丰富的控制:四个结果(允许、拒绝、询问或延迟)加上在执行前修改工具输入的能力。
当多个 PreToolUse hooks 返回不同的决定时,优先级是
deny > defer > ask > allow。
当 hook 返回 "ask" 时,向用户显示的权限提示包括一个标签,标识 hook 来自何处:例如,[User]、[Project]、[Plugin] 或 [Local]。这帮助用户了解哪个配置源正在请求确认。
AskUserQuestion 和 ExitPlanMode 需要用户交互,通常在非交互模式中使用 -p 标志时阻止。返回 permissionDecision: "allow" 以及 updatedInput 满足该要求:hook 从 stdin 读取工具的输入,通过您自己的 UI 收集答案,并在 updatedInput 中返回它,以便工具运行而不提示。仅返回 "allow" 对这些工具不足够。对于 AskUserQuestion,回显原始 questions 数组并添加一个answers对象,将每个问题的文本映射到选定的答案。
连接器工具您的组织设置为 ask即使 hook 返回 "allow" 也会提示。
从 v2.1.199 开始,一个 MCP 工具,其服务器用 _meta["anthropic/requiresUserInteraction"] 标记它,更严格:hook 不能用 "allow" 跳过其批准提示,无论是否有 updatedInput,因为 Claude Code 无法确认 hook 收集了工具需要的交互。
PreToolUse 之前使用顶级
decision 和 reason 字段,但这些对此事件已弃用。改用 hookSpecificOutput.permissionDecision 和 hookSpecificOutput.permissionDecisionReason。已弃用的值 "approve" 和 "block" 映射到 "allow" 和 "deny"。PostToolUse 和 Stop 等其他事件继续使用顶级 decision 和 reason 作为其当前格式。延迟工具调用以供稍后使用
"defer" 用于运行 claude -p 作为子进程并读取其 JSON 输出的集成,例如 Agent SDK 应用或构建在 Claude Code 之上的自定义 UI。它让该调用进程在工具调用处暂停 Claude,通过其自己的界面收集输入,并从中断处恢复。Claude Code 仅在非交互模式中使用 -p 标志时遵守此值。在交互式会话中,它记录警告并忽略 hook 结果。
AskUserQuestion 工具是典型情况:Claude 想要询问用户一些事情,但没有终端来回答。往返工作如下:
- Claude 调用
AskUserQuestion。PreToolUsehook 触发。 - Hook 返回
permissionDecision: "defer"。工具不执行。进程以stop_reason: "tool_deferred"退出,待处理的工具调用保留在成绩单中。 - 调用进程从 SDK 结果读取
deferred_tool_use,在其自己的 UI 中显示问题,并等待答案。 - 调用进程运行
claude -p --resume <session-id>。相同的工具调用再次触发PreToolUse。 - Hook 返回
permissionDecision: "allow"和updatedInput中的答案。工具执行,Claude 继续。
deferred_tool_use 字段携带工具的 id、name 和 input。input 是 Claude 为工具调用生成的参数,在执行前捕获:
cleanupPeriodDays 保留扫描的约束,该扫描默认在 30 天后删除会话文件。如果恢复时答案还没有准备好,hook 可以再次返回 "defer",进程以相同的方式退出。调用进程控制何时通过最终返回 "allow" 或 "deny" 从 hook 中断循环。
"defer" 仅在 Claude 在轮次中进行单个工具调用时有效。如果 Claude 一次进行多个工具调用,"defer" 被忽略并显示警告,工具通过正常权限流程进行。约束存在是因为恢复只能重新运行一个工具:没有办法延迟一个调用而不留下其他调用未解决。
如果恢复时延迟的工具不再可用,进程以 stop_reason: "tool_deferred_unavailable" 和 is_error: true 退出,在 hook 触发之前。这发生在为恢复的会话未连接提供工具的 MCP 服务器时。deferred_tool_use 有效负载仍然包括,以便您可以识别哪个工具丢失。
--resume 恢复工具被延迟时活跃的权限模式,因此您不需要再次传递 --permission-mode。例外是 plan 和 bypassPermissions,它们永远不会被携带。在恢复时显式传递 --permission-mode 会覆盖恢复的值。PermissionRequest
在向用户显示权限对话框时运行。使用PermissionRequest 决定控制代表用户允许或拒绝。 在工具名称上匹配,与 PreToolUse 相同的值。PermissionRequest 输入
PermissionRequest hooks 接收tool_name 和 tool_input 字段,如 PreToolUse hooks,但没有 tool_use_id。可选的 permission_suggestions 数组包含用户通常在权限对话框中看到的”总是允许”选项。区别在于 hook 何时触发:PermissionRequest hooks 在权限对话框即将显示给用户时运行,而 PreToolUse hooks 在工具执行前运行,无论权限状态如何。
PermissionRequest 决定控制
PermissionRequest hooks 可以允许或拒绝权限请求。除了所有 hooks 可用的JSON 输出字段外,您的 hook 脚本可以返回一个 decision 对象,其中包含这些事件特定字段:
权限更新条目
updatedPermissions 输出字段和permission_suggestions 输入字段都使用相同的条目对象数组。每个条目都有一个 type 来确定其其他字段,以及一个 destination 来控制更改的写入位置。
setMode 与 bypassPermissions 仅在会话已启动时生效,绕过模式已可用:--dangerously-skip-permissions、--permission-mode bypassPermissions、--allow-dangerously-skip-permissions 或设置中的 permissions.defaultMode: "bypassPermissions",且模式未被 permissions.disableBypassPermissionsMode 禁用。否则更新是无操作。bypassPermissions 无论 destination 如何都永远不会作为 defaultMode 持久化。destination 字段确定更改是保留在内存中还是持久化到设置文件。
Hook 可以回显它接收的
permission_suggestions 之一作为其自己的 updatedPermissions 输出,这等同于用户在对话框中选择该”总是允许”选项。
PostToolUse
在工具成功完成后立即运行。 在工具名称上匹配,与 PreToolUse 相同的值。PostToolUse 输入
PostToolUse hooks 在工具已经成功执行后触发。输入包括 tool_input(发送给工具的参数)和 tool_response(它返回的结果)。两者的确切架构取决于工具。
PostToolUse 决定控制
PostToolUse hooks 可以在工具执行后向 Claude 提供反馈。除了所有 hooks 可用的JSON 输出字段外,您的 hook 脚本可以返回这些事件特定字段:
下面的示例替换
Bash 调用的输出。替换值与 Bash 工具的输出形状匹配:
PostToolUseFailure
当工具执行失败时运行:工具抛出错误,或 MCP 工具返回错误结果。使用此来记录失败、发送警报或向 Claude 提供纠正反馈。 在工具名称上匹配,与 PreToolUse 相同的值。此事件不对工具调用在执行前被拒绝时触发:未知工具名称、输入失败架构或工具特定验证,或权限拒绝。验证拒绝作为
tool_use_error 结果返回,在 hooks 运行之前发生,因此它们既不触发 PreToolUse 也不触发此事件。权限拒绝触发 PreToolUse 但不触发此事件;请参阅PermissionDenied。PostToolUseFailure 输入
PostToolUseFailure hooks 接收与 PostToolUse 相同的tool_name 和 tool_input 字段,以及作为顶级字段的错误信息:
PostToolUseFailure 决定控制
PostToolUseFailure hooks 可以在工具失败后向 Claude 提供上下文。除了所有 hooks 可用的JSON 输出字段外,您的 hook 脚本可以返回这些事件特定字段:
PostToolBatch
在批次中的每个工具调用都已解决后运行一次,在 Claude Code 向模型发送下一个请求之前。PostToolUse 每个工具触发一次,这意味着当 Claude 进行并行工具调用时它并发触发。PostToolBatch 恰好触发一次,包含完整批次,因此它是注入取决于运行的工具集而不是任何单个工具的上下文的正确位置。此事件没有匹配器。
PostToolBatch 输入
除了通用输入字段外,PostToolBatch hooks 还接收tool_calls,一个描述批次中每个工具调用的数组:
tool_response 包含与模型在相应 tool_result 块中接收的内容相同的内容。该值是序列化的字符串或内容块数组,完全如工具发出的那样。对于 Read,这意味着行号前缀的文本而不是原始文件内容。响应可能很大,因此仅解析您需要的字段。
tool_response 形状与 PostToolUse 的不同。PostToolUse 传递工具的结构化 Output 对象,例如 {filePath: "...", success: true} 对于 Write;PostToolBatch 传递序列化的 tool_result 内容模型看到的。PostToolBatch 决定控制
PostToolBatch hooks 可以为 Claude 注入上下文。除了所有 hooks 可用的JSON 输出字段外,您的 hook 脚本可以返回这些事件特定字段:
decision: "block" 或 continue: false 在下一个模型调用之前停止代理循环。
PermissionDenied
当自动模式分类器拒绝工具调用时运行。此 hook 仅在自动模式中触发:当您手动拒绝权限对话框、PreToolUse hook 阻止调用或 deny 规则匹配时,它不运行。使用它来记录分类器拒绝、调整配置或告诉模型它可能重试工具调用。
在工具名称上匹配,与 PreToolUse 相同的值。
PermissionDenied 输入
除了通用输入字段外,PermissionDenied hooks 还接收tool_name、tool_input、tool_use_id 和 reason。
PermissionDenied 决定控制
PermissionDenied hooks 可以告诉模型它可能重试被拒绝的工具调用。返回一个 JSON 对象,其中hookSpecificOutput.retry 设置为 true:
retry 为 true 时,Claude Code 向对话添加一条消息,告诉模型它可能重试工具调用。拒绝本身不被反转。如果您的 hook 不返回 JSON,或返回 retry: false,拒绝成立,模型接收原始拒绝消息。
Notification
在 Claude Code 发送通知时运行。在通知类型上匹配。省略匹配器以为所有通知类型运行 hooks。agent_needs_input 和 agent_completed 类型需要 Claude Code v2.1.198 或更高版本。
使用单独的匹配器根据通知类型运行不同的处理程序。此配置在 Claude 需要权限批准时触发权限特定的警报脚本,在 Claude 空闲时触发不同的通知:
Notification 输入
除了通用输入字段外,Notification hooks 还接收message 和通知文本、可选的 title 和 notification_type 指示哪个类型触发。
systemMessage 适用。
SubagentStart
当通过 Agent 工具生成 Claude Code subagent 时运行。支持匹配器以按代理类型名称过滤。对于内置代理,这是代理名称,如general-purpose、Explore 或 Plan。对于自定义 subagents,这是代理 frontmatter 中的 name 字段,而不是文件名。
对于由插件提供的 subagents,代理类型是插件范围的标识符,例如 my-plugin:reviewer,而不是裸 frontmatter 名称。冒号将插件范围的名称放在正则表达式路径上,因此使用 ^ 和 $ 锚定匹配器以进行精确匹配:^my-plugin:reviewer$。
SubagentStart 输入
除了通用输入字段外,SubagentStart hooks 还接收agent_id 和 subagent 的唯一标识符以及 agent_type 和代理名称(匹配器过滤的值)。
SubagentStop
当 Claude Code subagent 完成响应时运行。在代理类型上匹配,与 SubagentStart 相同的值。SubagentStop 输入
除了通用输入字段外,SubagentStop hooks 还接收stop_hook_active、agent_id、agent_type、agent_transcript_path 和 last_assistant_message。agent_type 字段是用于匹配器过滤的值。transcript_path 是主会话的成绩单,而 agent_transcript_path 是 subagent 自己的成绩单,存储在嵌套的 subagents/ 文件夹中。last_assistant_message 字段包含 subagent 最终响应的文本内容,因此 hooks 可以访问它而无需解析成绩单文件。
SubagentStop hooks 还接收 Stop input 中描述的 background_tasks 和 session_crons 数组,在 Claude Code v2.1.145 或更高版本中可用。两个数组都限定于父会话,而不是 subagent。
hookSpecificOutput.additionalContext 和 hookEventName 设置为 "SubagentStop",用于非错误反馈以保持 subagent 运行。返回 decision: "block" 和 reason 保持 subagent 运行并将 reason 作为其下一个指令传递给 subagent。要在 subagent 返回后向父会话注入上下文,请改用 Agent 工具上的PostToolUse hook。
TaskCreated
当通过TaskCreate 工具创建任务时运行。使用此来强制执行命名约定、要求任务描述或防止创建某些任务。
当 TaskCreated hook 以代码 2 退出时,任务不被创建,stderr 消息作为反馈反馈给模型。要完全停止队友而不是重新运行它,返回 JSON {"continue": false, "stopReason": "..."} 。TaskCreated hooks 不支持匹配器,在每次出现时触发。
TaskCreated 输入
除了通用输入字段外,TaskCreated hooks 还接收task_id、task_subject 和可选的 task_description、teammate_name 和 team_name。
TaskCreated 决定控制
TaskCreated hooks 支持两种方式来控制任务创建:- 退出代码 2:任务不被创建,stderr 消息作为反馈反馈给模型。
- JSON
{"continue": false, "stopReason": "..."}:完全停止队友,匹配Stophook 行为。stopReason向用户显示。
TaskCompleted
当任务被标记为已完成时运行。这在两种情况下触发:当任何代理通过 TaskUpdate 工具显式标记任务为已完成时,或当代理团队队友完成其轮次且有进行中的任务时。使用此来强制执行完成标准,如通过测试或 lint 检查,然后任务才能关闭。 当TaskCompleted hook 以代码 2 退出时,任务不被标记为已完成,stderr 消息作为反馈反馈给模型。要完全停止队友而不是重新运行它,返回 JSON {"continue": false, "stopReason": "..."} 。TaskCompleted hooks 不支持匹配器,在每次出现时触发。
TaskCompleted 输入
除了通用输入字段外,TaskCompleted hooks 还接收task_id、task_subject 和可选的 task_description、teammate_name 和 team_name。
TaskCompleted 决定控制
TaskCompleted hooks 支持两种方式来控制任务完成:- 退出代码 2:任务不被标记为已完成,stderr 消息作为反馈反馈给模型。
- JSON
{"continue": false, "stopReason": "..."}:完全停止队友,匹配Stophook 行为。stopReason向用户显示。
Stop
在主 Claude Code 代理完成响应时运行。如果停止是由于用户中断,则不运行。API 错误触发StopFailure。Stop 输入
除了通用输入字段外,Stop hooks 还接收stop_hook_active、last_assistant_message、background_tasks 和 session_crons。stop_hook_active 字段在 Claude Code 已经作为 stop hook 的结果继续时为 true。检查此值或处理成绩单以防止 Claude Code 无限运行。Claude Code 在 8 次连续阻止后覆盖 hook 并结束轮次。
last_assistant_message 字段包含 Claude 最终响应的文本内容,因此 hooks 可以访问它而无需解析成绩单文件。
background_tasks 和 session_crons 数组在 Claude Code v2.1.145 或更高版本中可用,让 hooks 区分”会话完成”和”会话暂停等待后台工作唤醒它”。当任务注册表可达时两个数组都存在,当没有任何内容在进行中或计划时为空。
background_tasks 中的每个条目描述一个进行中的任务,并使用这些字段:
session_crons 中的每个条目描述一个会话范围的计划唤醒,来自 CronCreate、ScheduleWakeup 和 /loop:
此示例显示一个 Stop 输入,其中有一个进行中的 shell 任务和一个循环 cron:
Stop 决定控制
Stop 和 SubagentStop hooks 可以控制 Claude 是否继续。除了所有 hooks 可用的JSON 输出字段外,您的 hook 脚本可以返回这些事件特定字段:
additionalContext,例如”在完成前运行测试套件”。它通过与 decision: "block" 相同的循环保护保持对话进行,即 stop_hook_active 输入和 8 次连续继续上限,但成绩单将其标记为 Stop hook feedback,不显示 hook 错误通知:
StopFailure
当轮次因 API 错误而结束时运行,而不是Stop。输出和退出代码被忽略。使用此来记录失败、发送警报或在 Claude 因速率限制、身份验证问题或其他 API 错误而无法完成响应时采取恢复操作。StopFailure 输入
除了通用输入字段外,StopFailure hooks 还接收error、可选的 error_details 和可选的 last_assistant_message。error 字段标识错误类型,用于匹配器过滤。
TeammateIdle
当代理团队队友在完成其轮次后即将空闲时运行。使用此来强制执行质量门,如要求通过 lint 检查或验证输出文件存在。 当TeammateIdle hook 以代码 2 退出时,队友接收 stderr 消息作为反馈并继续工作而不是空闲。要完全停止队友而不是重新运行它,返回 JSON {"continue": false, "stopReason": "..."} 。TeammateIdle hooks 不支持匹配器,在每次出现时触发。
TeammateIdle 输入
除了通用输入字段外,TeammateIdle hooks 还接收teammate_name 和 team_name。
TeammateIdle 决定控制
TeammateIdle hooks 支持两种方式来控制队友行为:- 退出代码 2:队友接收 stderr 消息作为反馈并继续工作而不是空闲。
- JSON
{"continue": false, "stopReason": "..."}:完全停止队友,匹配Stophook 行为。stopReason向用户显示。
ConfigChange
当会话期间配置文件更改时运行。使用此来审计设置更改、强制执行安全策略或阻止对配置文件的未授权修改。 ConfigChange hooks 对设置文件、托管策略设置和 skill 文件的更改触发。输入中的source 字段告诉您哪种类型的配置更改,可选的 file_path 字段提供更改文件的路径。
匹配器在配置源上过滤:
此示例记录所有配置更改以进行安全审计:
ConfigChange 输入
除了通用输入字段外,ConfigChange hooks 还接收source 和可选的 file_path。source 字段指示哪种配置类型更改,file_path 提供被修改的特定文件的路径。
ConfigChange 决定控制
ConfigChange hooks 可以阻止配置更改生效。使用退出代码 2 或 JSONdecision 来防止更改。被阻止时,新设置不应用于运行中的会话。
policy_settings 更改无法被阻止。Hooks 仍然对 policy_settings 源触发,因此您可以使用它们进行审计日志记录,但任何阻止决定都被忽略。这确保企业管理的设置始终生效。
CwdChanged
当会话期间工作目录更改时运行,例如当 Claude 执行cd 命令时。使用此来对目录更改做出反应:重新加载环境变量、激活项目特定的工具链或自动运行设置脚本。与FileChanged配对,用于direnv等管理每个目录环境的工具。
CwdChanged hooks 可以访问 CLAUDE_ENV_FILE。写入该文件的变量持久化到会话的后续 Bash 命令中,就像在SessionStart hooks中一样。
CwdChanged 不支持匹配器,在每次目录更改时触发。
CwdChanged 输入
除了通用输入字段外,CwdChanged hooks 还接收old_cwd 和 new_cwd。
CwdChanged 输出
除了所有 hooks 可用的JSON 输出字段外,CwdChanged hooks 还可以返回watchPaths 来动态设置FileChanged监视的文件路径:
CwdChanged hooks 没有决定控制。它们无法阻止目录更改。
FileChanged
当监视的文件在磁盘上更改时运行。用于在项目配置文件修改时重新加载环境变量。 此事件的matcher 有两个作用:
- 构建监视列表:值在
|上分割,每个段注册为工作目录中的文字文件名,因此".envrc|.env"监视恰好这两个文件。正则表达式模式在这里不有用:像^\.env这样的值会监视一个字面上名为^\.env的文件。 - 过滤哪些 hooks 运行:当监视的文件更改时,相同的值使用标准匹配器规则针对更改文件的基名过滤哪些 hook 组运行。
CLAUDE_ENV_FILE。写入该文件的变量持久化到会话的后续 Bash 命令中,就像在SessionStart hooks中一样。
FileChanged 输入
除了通用输入字段外,FileChanged hooks 还接收file_path 和 event。
FileChanged 输出
除了所有 hooks 可用的JSON 输出字段外,FileChanged hooks 还可以返回watchPaths 来动态更新监视的文件路径:
FileChanged hooks 没有决定控制。它们无法阻止文件更改的发生。
WorktreeCreate
当您运行claude --worktree 或subagent 使用 isolation: "worktree"时运行。默认情况下,Claude Code 使用 git worktree 创建隔离的工作副本。配置 WorktreeCreate hook 替换该默认 git 行为,让您使用不同的版本控制系统,如 SVN、Perforce 或 Mercurial。
因为 hook 完全替换默认行为,.worktreeinclude不被处理。如果您需要将本地配置文件(如 .env)复制到新 worktree,请在您的 hook 脚本内执行。
Hook 必须返回创建的 worktree 目录的绝对路径。Claude Code 使用此路径作为隔离会话的工作目录。请参阅WorktreeCreate 输出了解每个 hook 类型如何返回路径。
此示例创建 SVN 工作副本并打印路径供 Claude Code 使用。用您自己的替换仓库 URL:
name,将新副本检出到新目录,并打印目录路径。最后一行的 echo 是 Claude Code 读取的 worktree 路径。将任何其他输出重定向到 stderr,以便它不会干扰路径。
WorktreeCreate 输入
除了通用输入字段外,WorktreeCreate hooks 还接收name 字段。这是新 worktree 的 slug 标识符,由用户指定或自动生成,例如 bold-oak-a3f2。
WorktreeCreate 输出
WorktreeCreate hooks 不使用标准的允许/阻止决定模型。相反,hook 的成功或失败决定结果。Hook 必须返回创建的 worktree 目录的绝对路径:- 命令 hooks(
type: "command"):在 stdout 上打印路径作为最后一个非空行。Claude Code 在读取该行之前剥离 ANSI 转义代码,因此在您的echo之前打印的 shell 启动横幅被忽略。将任何其他 hook 输出重定向到 stderr。 - HTTP hooks(
type: "http"):在响应体中返回{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }。
-p 时会停滞约 30 秒,然后以代码 0 退出。
WorktreeRemove
当 worktree 被移除时运行,要么当您退出--worktree 会话并选择移除它时,要么当具有 isolation: "worktree" 的 subagent 完成时。这是WorktreeCreate的清理对应物。
对于基于 git 的 worktrees,Claude Code 使用 git worktree remove 自动处理清理。如果您为非 git 版本控制系统配置了 WorktreeCreate hook,将其与 WorktreeRemove hook 配对以处理清理。没有它,worktree 目录留在磁盘上。
Claude Code 将 WorktreeCreate 返回的路径作为 worktree_path 在 hook 输入中传递。此示例读取该路径并移除目录:
WorktreeRemove 输入
除了通用输入字段外,WorktreeRemove hooks 还接收worktree_path 字段,这是被移除的 worktree 的绝对路径。
PreCompact
在 Claude Code 即将运行压缩操作之前运行。 匹配器值指示压缩是手动还是自动触发:
退出代码 2 以阻止压缩。对于手动
/compact,stderr 消息向用户显示。您也可以通过返回带有 "decision": "block" 的 JSON 来阻止。
阻止自动压缩有不同的效果,取决于何时触发。如果压缩在上下文限制之前主动触发,Claude Code 跳过它,对话继续未压缩。如果压缩被触发以从已由 API 返回的上下文限制错误恢复,底层错误浮出并且当前请求失败。
PreCompact 输入
除了通用输入字段外,PreCompact hooks 还接收trigger 和 custom_instructions。对于 manual,custom_instructions 包含用户传入 /compact 的内容。对于 auto,custom_instructions 为空。
PostCompact
在 Claude Code 完成压缩操作后运行。使用此事件对新的压缩状态做出反应,例如记录生成的摘要或更新外部状态。 与PreCompact 相同的匹配器值适用:
PostCompact 输入
除了通用输入字段外,PostCompact hooks 还接收trigger 和 compact_summary。compact_summary 字段包含压缩操作生成的对话摘要。
SessionEnd
当 Claude Code 会话结束时运行。用于清理任务、记录会话统计或保存会话状态。支持匹配器以按退出原因过滤。 hook 输入中的reason 字段指示会话为何结束:
SessionEnd 输入
除了通用输入字段外,SessionEnd hooks 还接收reason 字段,指示会话为何结束。有关所有值,请参阅上面的原因表。
/clear 和通过交互式 /resume 切换会话。如果 hook 需要更多时间,在 hook 配置中设置 timeout。总体预算自动提高到配置的最高每个 hook 超时,最多 60 秒。在插件提供的 hooks 上设置的超时不会提高预算。要显式覆盖预算,请在毫秒中设置 CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 环境变量。
Elicitation
当 MCP 服务器在任务中途请求用户输入时运行。默认情况下,Claude Code 显示交互式对话供用户响应。Hooks 可以拦截此请求并以编程方式响应,完全跳过对话。 匹配器字段与 MCP 服务器名称匹配。Elicitation 输入
除了通用输入字段外,Elicitation hooks 还接收mcp_server_name、message 和可选的 mode、url、elicitation_id 和 requested_schema 字段。
对于 form 模式 elicitation(最常见的情况):
Elicitation 输出
要以编程方式响应而不显示对话,返回带有hookSpecificOutput 的 JSON 对象:
退出代码 2 拒绝 elicitation 并向用户显示 stderr。
ElicitationResult
在用户响应 MCP elicitation 后运行。Hooks 可以观察、修改或阻止响应,然后将其发送回 MCP 服务器。 匹配器字段与 MCP 服务器名称匹配。ElicitationResult 输入
除了通用输入字段外,ElicitationResult hooks 还接收mcp_server_name、action 和可选的 mode、elicitation_id 和 content 字段。
ElicitationResult 输出
要覆盖用户的响应,返回带有hookSpecificOutput 的 JSON 对象:
退出代码 2 阻止响应,将有效操作更改为
decline。
基于提示的 hooks
除了命令、HTTP 和 MCP tool hooks 外,Claude Code 还支持基于提示的 hooks(type: "prompt"),使用 LLM 来评估是否允许或阻止操作,以及代理 hooks(type: "agent"),生成具有工具访问权限的代理验证器。并非所有事件都支持每种 hook 类型。
支持所有五种 hook 类型(command、http、mcp_tool、prompt 和 agent)的事件:
PermissionDeniedPermissionRequestPostToolBatchPostToolUsePostToolUseFailurePreToolUseStopSubagentStopTaskCompletedTaskCreatedTeammateIdleUserPromptExpansionUserPromptSubmit
command、http 和 mcp_tool hooks 但不支持 prompt 或 agent 的事件:
ConfigChangeCwdChangedElicitationElicitationResultFileChangedInstructionsLoadedNotificationPostCompactPreCompactSessionEndStopFailureSubagentStartWorktreeCreateWorktreeRemove
SessionStart 和 Setup 支持 command 和 mcp_tool hooks。它们不支持 http、prompt 或 agent hooks。
基于提示的 hooks 如何工作
基于提示的 hooks 不执行 Bash 命令,而是:- 将 hook 输入和您的提示发送到 Claude 模型,默认为 Haiku
- LLM 使用包含决定的结构化 JSON 响应
- Claude Code 自动处理决定
提示 hook 配置
将type 设置为 "prompt" 并提供 prompt 字符串而不是 command。使用 $ARGUMENTS 占位符将 hook 的 JSON 输入数据注入到您的提示文本中。Claude Code 将组合的提示和输入发送到快速 Claude 模型,该模型返回 JSON 决定。
此 Stop hook 要求 LLM 在允许 Claude 完成之前评估是否应该停止:
响应架构
LLM 必须使用包含以下内容的 JSON 响应:ok: false 时发生的情况取决于事件:
Stop和SubagentStop:原因被反馈给 Claude 作为其下一条指令,转轮继续PreToolUse:工具调用被拒绝,原因作为工具错误返回给 Claude,等同于命令 hook 的permissionDecision: "deny"PostToolUse:默认情况下转轮结束,原因在聊天中显示为警告行。设置continueOnBlock: true以将原因反馈给 Claude 并继续转轮PostToolBatch、UserPromptSubmit和UserPromptExpansion:转轮结束,原因显示为警告行。这些事件在decision: "block"上结束转轮,无论continue如何PostToolUseFailure、TaskCreated和TaskCompleted:原因作为工具错误返回给 Claude,类似于PreToolUseTeammateIdle:默认情况下队友停止,原因显示为警告行。设置continueOnBlock: true以将原因反馈给队友并保持其继续工作PermissionRequest:ok: false无效。要从 hook 拒绝批准,请使用命令 hook,返回hookSpecificOutput.decision.behavior: "deny"PermissionDenied:ok: false无效,因为拒绝已经发生。此事件读取的唯一输出是hookSpecificOutput.retry,提示和代理 hooks 无法设置 — 它们在此事件上运行,但其输出被丢弃。使用命令 hook返回retry
检查多个条件后再停止
此Stop hook 使用详细提示检查三个条件,然后允许 Claude 停止。SubagentStop hooks 使用相同的格式来评估子代理是否应该停止。如果 "ok" 为 false,Claude 继续工作,提供的原因作为其下一条指令:
基于代理的 hooks
基于代理的 hooks(type: "agent")类似于基于提示的 hooks,但具有多轮工具访问。代理 hook 生成一个可以读取文件、搜索代码和检查代码库以验证条件的 subagent,而不是单个 LLM 调用。代理 hooks 支持与基于提示的 hooks 相同的事件。
基于代理的 hooks 如何工作
当代理 hook 触发时:- Claude Code 生成一个 subagent,带有您的提示和 hook 的 JSON 输入
- Subagent 可以使用 Read、Grep 和 Glob 等工具进行调查
- 在最多 50 轮后,subagent 返回结构化的
{ "ok": true/false }决定 - Claude Code 以与提示 hook 相同的方式处理决定
代理 hook 配置
将type 设置为 "agent" 并提供 prompt 字符串。配置字段与提示 hooks相同,但超时更长:
响应架构与提示 hooks 相同:
{ "ok": true } 允许或 { "ok": false, "reason": "..." } 阻止。
此 Stop hook 验证所有单元测试通过,然后允许 Claude 完成:
在后台运行 Hooks
默认情况下,hooks 阻止 Claude 的执行,直到它们完成。对于长时间运行的任务,如部署、测试套件或外部 API 调用,设置"async": true 以在后台运行 hook,同时 Claude 继续工作。异步 hooks 无法阻止或控制 Claude 的行为:响应字段如 decision、permissionDecision 和 continue 无效,因为它们会控制的操作已经完成。
配置异步 Hook
将"async": true 添加到命令 hook 的配置以在后台运行它而不阻止 Claude。此字段仅在 type: "command" hooks 上可用。
此 hook 在每个 Write 工具调用后运行测试脚本。Claude 立即继续工作,同时 run-tests.sh 执行最多 120 秒。脚本完成时,其输出在下一个对话轮次上传递:
timeout 字段设置后台进程的最大时间(秒)。如果未指定,异步 hooks 使用与同步 hooks 相同的 10 分钟默认值。
异步 Hooks 如何执行
当异步 hook 触发时,Claude Code 启动 hook 进程并立即继续,不等待其完成。Hook 通过 stdin 接收与同步 hook 相同的 JSON 输入。 后台进程退出后,如果 hook 产生了带有additionalContext 字段的 JSON 响应,该内容在下一个对话轮次作为上下文传递给 Claude。systemMessage 字段显示给你,而不是 Claude。
Claude Code 根据与同步 hooks 相同的输出架构验证 JSON 响应,并删除任何值类型错误的字段,例如不是字符串的 systemMessage,而不是传递它。使用 --debug 运行以查看命名每个删除字段的警告。在 v2.1.202 之前,来自异步 hook 的格式错误的 JSON 输出可能会导致会话崩溃,并且每次恢复会话时崩溃都会重复发生。
异步 hook 完成通知默认被抑制。要查看它们,请使用 Ctrl+O 启用详细模式或使用 --verbose 启动 Claude Code。
文件更改后运行测试
此 hook 在 Claude 写入文件时在后台启动测试套件,然后在测试完成时将结果报告回 Claude。将此脚本保存到项目中的.claude/hooks/run-tests-async.sh 并使用 chmod +x 使其可执行:
.claude/settings.json。async: true 标志让 Claude 在测试运行时继续工作:
限制
异步 hooks 与同步 hooks 相比有几个限制:- 仅
type: "command"hooks 支持async。基于提示的 hooks 无法异步运行。 - 异步 hooks 无法阻止工具调用或返回决定。到 hook 完成时,触发操作已经进行。
- Hook 输出在下一个对话轮次传递。如果会话空闲,响应等待直到下一个用户交互。例外:
asyncRewakehook 在退出代码 2 时立即唤醒 Claude,即使会话空闲。 - 每次执行创建一个单独的后台进程。同一异步 hook 的多个触发之间没有去重。
安全考虑
免责声明
命令 hooks 使用您的系统用户的完整权限运行。安全最佳实践
编写 hooks 时请记住这些实践:- 验证和清理输入:永远不要盲目信任输入数据
- 始终引用 shell 变量:使用
"$VAR"而不是$VAR - 阻止路径遍历:检查文件路径中的
.. - 使用绝对路径:为脚本指定完整路径。在 exec 形式中,使用
${CLAUDE_PROJECT_DIR}且路径无需引用。在 shell 形式中,将其包装在双引号中 - 跳过敏感文件:避免
.env、.git/、密钥等
Windows PowerShell 工具
在 Windows 上,您可以通过在命令 hook 上设置"shell": "powershell" 在 PowerShell 中运行单个 hooks。Hooks 直接生成 PowerShell,因此这适用于是否设置了 CLAUDE_CODE_USE_POWERSHELL_TOOL。Claude Code 自动检测 pwsh.exe(PowerShell 7 及更高版本的可执行文件),并回退到 powershell.exe(Windows PowerShell 5.1)。
${CLAUDE_PROJECT_DIR} 或 $env:CLAUDE_PROJECT_DIR。从 v2.1.198 开始,Claude Code 会将 PowerShell shell 形式命令中的 ${CLAUDE_PROJECT_DIR}、${CLAUDE_PLUGIN_ROOT} 和 ${CLAUDE_PLUGIN_DATA} 占位符重写为 PowerShell 的 ${env:NAME} 形式,无论 hook 是在 settings.json、插件还是技能中定义。PowerShell 在解析后从导出的环境中解析该值,因此占位符在双引号字符串内有效,但在单引号字符串内无效,PowerShell 在单引号字符串中永远不会展开变量。
在 v2.1.198 之前,此重写仅适用于插件 hooks。在早期版本上,settings.json hook 需要 $env: 形式或 exec 形式,其中 ${CLAUDE_PROJECT_DIR} 在每个 args 元素中被替换,无论 hook 在何处定义。
不要在 PowerShell hook 中写入裸 $CLAUDE_PROJECT_DIR 拼写。PowerShell 将其解析为未定义的本地变量,并将其解析为 $null,这会导致脚本路径没有其项目根前缀。Claude Code 不会重写该形式;它会在 debug log 中记录警告。
下面的示例显示了一个 settings.json hook,它使用 $env: 形式运行项目脚本,该形式在每个版本上都有效:
调试 hooks
Hook 执行详细信息,包括哪些 hooks 匹配、它们的退出代码和完整 stdout 和 stderr,被写入调试日志文件。使用claude --debug-file <path> 启动 Claude Code 以将日志写入已知位置,或运行 claude --debug 并在 ~/.claude/debug/<session-id>.txt 读取日志。--debug 标志不打印到终端。
CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose 以查看额外的日志行,例如 hook 匹配器计数和查询匹配。
有关故障排除常见问题,如 hooks 不触发、Stop hooks 持续阻止或配置错误,请参阅指南中的限制和故障排除。有关涵盖 /context、/doctor 和设置优先级的更广泛的诊断演练,请参阅调试你的配置。