SpyBara
Go Premium

hooks.md 2026-09-21 22:59 UTC to 2026-09-22 23:59 UTC

This page contains 240 additions and 237 deletions.

2026
Sat 19 23:57 Tue 22 23:59 Fri 25 23:58

Hooks 参考

Claude Code hook 事件、配置架构、JSON 输入/输出格式、退出代码、异步 hooks、HTTP hooks、提示 hooks 和 MCP 工具 hooks 的参考。

Hooks 是用户定义的 shell 命令、HTTP 端点、MCP 工具调用、LLM 提示或子代理,在 Claude Code 生命周期中的特定点自动执行。Claude Code 在其运行的任何地方都会触发相同的 hook 事件:终端中的会话、IDE 扩展、桌面应用和云会话。使用此参考查找事件架构、配置选项、JSON 输入/输出格式以及异步 hooks、HTTP hooks 和 MCP 工具 hooks 等高级功能。

Hook 生命周期

Claude Code 在会话期间的特定点运行 hooks。当事件触发且匹配器匹配时,Claude Code 会将关于该事件的 JSON 上下文传递给您的 hook 处理程序。对于命令 hooks,输入通过 stdin 到达。对于 HTTP hooks,它作为 POST 请求体到达。您的处理程序随后可以检查输入、采取行动并可选地返回决定。

事件分为三种频率:

  • 每个会话一次:SessionStart 和 SessionEnd
  • 每轮一次:UserPromptSubmit、Stop 和 StopFailure
  • 代理循环内的每个工具调用:PreToolUse 和 PostToolUse,除了 EndConversation 调用,它们跳过两者
Hook 生命周期图,显示可选的 Setup 流入 SessionStart,然后是每轮循环,包含 UserPromptSubmit、用于 slash commands 的 UserPromptExpansion、嵌套的代理循环(PreToolUse、PermissionRequest、PostToolUse、PostToolUseFailure、PostToolBatch、SubagentStart/Stop、TaskCreated、TaskCompleted)和 Stop 或 StopFailure,然后是 TeammateIdle、PreCompact、PostCompact 和 SessionEnd,Elicitation 和 ElicitationResult 嵌套在 MCP 工具执行内,PermissionDenied 作为 PermissionRequest 的副分支用于自动模式拒绝,WorktreeCreate、WorktreeRemove、Notification、ConfigChange、InstructionsLoaded、CwdChanged、FileChanged 和 DirectoryAdded 作为独立异步事件,PreModelSwitch 作为独立顺序事件,在请求的模型切换之前运行,PostModelSwitch 作为独立异步事件,在会话的模型更改后运行,MessageDisplay 作为仅显示事件,在助手消息文本流式传输时运行
<img src="https://mintcdn.com/claude-code/x7pO8l4XcvAXCoVc/images/hooks-lifecycle-dark.svg?fit=max&auto=format&n=x7pO8l4XcvAXCoVc&q=85&s=c9b3d88487335f58cce0b52e2f9e7531" className="hidden dark:block" alt="Hook 生命周期图,显示可选的 Setup 流入 SessionStart,然后是每轮循环,包含 UserPromptSubmit、用于 slash commands 的 UserPromptExpansion、嵌套的代理循环(PreToolUse、PermissionRequest、PostToolUse、PostToolUseFailure、PostToolBatch、SubagentStart/Stop、TaskCreated、TaskCompleted)和 Stop 或 StopFailure,然后是 TeammateIdle、PreCompact、PostCompact 和 SessionEnd,Elicitation 和 ElicitationResult 嵌套在 MCP 工具执行内,PermissionDenied 作为 PermissionRequest 的副分支用于自动模式拒绝,WorktreeCreate、WorktreeRemove、Notification、ConfigChange、InstructionsLoaded、CwdChanged、FileChanged 和 DirectoryAdded 作为独立异步事件,PreModelSwitch 作为独立顺序事件,在请求的模型切换之前运行,PostModelSwitch 作为独立异步事件,在会话的模型更改后运行,MessageDisplay 作为仅显示事件,在助手消息文本流式传输时运行" width="520" height="1336" data-path="images/hooks-lifecycle-dark.svg" />

下表总结了每个事件何时触发。Hook 事件部分记录了每个事件的完整输入架构和决定控制选项。

事件 触发时机
SessionStart 当会话开始或恢复时
Setup 当你使用 --init-only 启动 Claude Code,或在 -p 模式下使用 --init 或 --maintenance 时。用于 CI 或脚本中的一次性准备
UserPromptSubmit 当你提交提示词时,在 Claude 处理之前
UserPromptExpansion 当用户输入的命令扩展为提示词时,在到达 Claude 之前。可以阻止扩展
PreToolUse 在工具调用执行之前。可以阻止它
PermissionRequest 当工具调用需要权限决策时
PermissionDenied 当自动模式拒绝工具调用时,包括没有分类器判决的拒绝。使用 JSON hookSpecificOutput.retry: true 来告诉模型它可以重试被拒绝的工具调用。Claude Code 在分类器未产生判决时忽略 retry
PostToolUse 在工具调用成功后
PostToolUseFailure 在工具调用失败后
PostToolBatch 在一整批并行工具调用解决后,在下一次模型调用之前
Notification 当 Claude Code 发送通知时
MessageDisplay 当助手消息文本正在显示时
SubagentStart 当子代理被生成时
SubagentStop 当子代理完成时
TaskCreated 当通过 TaskCreate 创建任务时
TaskCompleted 当任务被标记为已完成时
Stop 当 Claude 完成响应时
StopFailure 当轮次因 API 错误而结束时
TeammateIdle 当代理团队队友即将空闲时
InstructionsLoaded 当 CLAUDE.md 或 .claude/rules/*.md 文件被加载到上下文中时。在会话开始时和文件在会话期间被延迟加载时触发
ConfigChange 当配置文件在会话期间更改时
CwdChanged 当工作目录更改时,例如当 Claude 执行 cd 命令时。对于使用 direnv 等工具的反应式环境管理很有用
DirectoryAdded 当工作目录在会话中期通过 /add-dir 或 SDK register_repo_root 控制请求添加时
FileChanged 当监视的文件在磁盘上更改时。matcher 字段指定要监视的文件名
WorktreeCreate 当通过 --worktree、isolation: "worktree" 创建工作树时,或用于后台会话。替换默认的 git 行为
WorktreeRemove 当在会话退出时、子代理完成时或删除后台会话时移除工作树
PreCompact 在上下文压缩之前
PostCompact 在上下文压缩完成后
PreModelSwitch 在 Claude Code 应用你或客户端请求的模型切换之前。可以阻止切换
PostModelSwitch 在会话的模型更改后,包括 Claude Code 自己进行的更改,例如在你恢复会话时恢复模型
Elicitation 当 MCP 服务器在工具调用期间请求用户输入时
ElicitationResult 在用户响应 MCP 引出后,在响应发送回服务器之前
SessionEnd 当会话终止时

Hook 如何解析

要了解事件、匹配器和处理程序如何组合在一起,请考虑这个 PreToolUse hook,它阻止破坏性 shell 命令。

matcher 缩小到 Bash 工具调用,if 条件进一步缩小到匹配 rm * 的 Bash 子命令,因此 block-rm.sh 仅在两个过滤器都匹配时生成:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}

该脚本从 stdin 读取 JSON 输入,提取命令,如果包含 rm -rf,则返回 permissionDecision 为 "deny"。将其保存到项目中的 .claude/hooks/block-rm.sh,并使用 chmod +x .claude/hooks/block-rm.sh 使其可执行,以便 Claude Code 可以运行它:

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0  # no decision; normal permission flow applies
fi

此脚本与本页面上解析 JSON 输入的其他 Bash 示例一样,使用 jq,因此在尝试它们之前,请安装 jq 并确保它在您的 PATH 上。

现在假设 Claude Code 决定针对 macOS/Linux 配置运行 Bash "rm -rf /tmp/build"。以下是发生的情况:

Hook 解析流程图:PreToolUse 触发,匹配器检查 Bash 匹配,然后 if 条件检查 Bash(rm *) 匹配。如果两者都匹配,hook 命令运行并返回 permissionDecision deny,因此工具调用被阻止,Claude Code 继续。如果任一检查未能匹配,hook 被跳过,工具调用被允许继续。 Hook 解析流程图:PreToolUse 触发,匹配器检查 Bash 匹配,然后 if 条件检查 Bash(rm *) 匹配。如果两者都匹配,hook 命令运行并返回 permissionDecision deny,因此工具调用被阻止,Claude Code 继续。如果任一检查未能匹配,hook 被跳过,工具调用被允许继续。
1

事件触发

PreToolUse 事件触发。Claude Code 将工具输入作为 JSON 通过 stdin 发送到 hook:

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
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:

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}

如果命令是更安全的 rm 变体,如 rm file.txt,脚本会改为执行 exit 0。退出代码 0 且无输出意味着 hook 没有决定要报告,因此工具调用继续通过正常的权限流程。hook 可以拒绝调用,但保持沉默不会批准它。

5

Claude Code 对结果采取行动

Claude Code 读取 JSON 决定,阻止工具调用,并向 Claude 显示原因。

下面的配置部分记录了完整的架构,每个hook 事件部分记录了您的命令接收的输入以及它可以返回的输出。

配置

Hooks 在 JSON 设置文件中定义。配置有三个嵌套级别:

  1. 选择要响应的hook 事件,如 PreToolUse 或 Stop
  2. 添加匹配器组以过滤何时触发,如"仅针对 Bash 工具"
  3. 定义一个或多个hook 处理程序以在匹配时运行

有关完整的演练和带注释的示例,请参阅上面的Hook 如何解析。

Hook 位置

您定义 hook 的位置决定了其范围:

位置 范围 可共享
~/.claude/settings.json 您的所有项目 否,本地于您的计算机
.claude/settings.json 单个项目 是,可以提交到仓库
.claude/settings.local.json 单个项目 否,gitignored 当 Claude Code 保存设置到其中时
托管策略设置 组织范围 是,管理员控制
Plugin hooks/hooks.json 启用插件时 是,与插件捆绑
Skill frontmatter 调用 skill 后的会话其余部分。请参阅Skills 和代理中的 Hooks 是,在 skill 文件中定义
Subagent frontmatter 该 subagent 运行时 是,在 subagent 文件中定义

Cloud sessions不读取您的本地 ~/.claude/settings.json;那里的 hooks 来自仓库的 .claude/settings.json 在具有一个仓库的会话中,来自从您的 claude.ai 账户同步的插件,以及来自您组织的服务器管理的设置。在自托管环境中,Claude Code 也运行操作员从运行程序主机的 ~/.claude/ 中播种的 hooks,并且当该文件在Claude Code 应用的托管源中时,它运行运行程序镜像的托管设置文件中的 hooks,默认情况下仅当服务器管理的设置和 MDM 交付的 Claude Code 策略都不提供托管层时。有关哪些文件到达云会话,请参阅您的设置中携带的内容。

有关设置文件解析的详细信息,请参阅设置。

来自设置文件、托管策略设置和插件的 Hooks 也在subagents内运行。当 subagent 调用工具时,工具事件(如 PreToolUse 和 PostToolUse)触发与主对话中配置的相同 hooks,输入携带 agent_id 和 agent_type通用输入字段,用于标识 subagent。

企业管理员可以使用 allowManagedHooksOnly 来限制哪些 hooks 运行:

请参阅在 allowManagedHooksOnly 下运行的内容。

Hook 条目在设置级别之间合并而不是相互替换:用户、项目和本地设置添加它们自己的 hooks 而不移除托管的,disableAllHooks设置无法从托管设置外部禁用托管 hooks。

HTTP hook 允许列表适用于来自每个源的 hooks,包括托管策略设置:

  • allowedHttpHookUrls:在任何设置级别定义时,Claude Code 仅在其 URL 与合并的允许列表匹配时运行 HTTP hook 处理程序
  • httpHookAllowedEnvVars:定义时,Claude Code 仅将该列表上的环境变量插值到 hook 标头中

匹配器模式

matcher 字段过滤 hooks 何时触发。匹配器的评估方式取决于它包含的字符:

匹配器值 评估为 示例
"*"、"" 或省略 匹配所有 在事件的每次出现时触发
仅字母、数字、_、-、空格、, 和 | 精确字符串或由 | 或 , 分隔的精确字符串列表,可选周围空格 Bash 仅匹配 Bash 工具;Edit|Write 和 Edit, Write 各自精确匹配任一工具;code-reviewer 仅匹配该代理类型
包含任何其他字符 JavaScript 正则表达式,未锚定 ^Notebook 匹配任何以 Notebook 开头的工具;mcp__memory__.* 匹配来自 memory 服务器的每个工具

在正则表达式路径上的匹配器使用 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。

每个事件类型在不同的字段上匹配:

事件 匹配器过滤的内容 示例匹配器值
PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied 工具名称 Bash、Edit|Write、mcp__.*
SessionStart 会话如何启动 startup、resume、clear、compact、fork
Setup 哪个 CLI 标志触发了设置 init、maintenance
SessionEnd 会话为何结束 clear、resume、logout、prompt_input_exit、other
Notification 通知类型 permission_prompt、idle_prompt、auth_success、elicitation_dialog、elicitation_url_dialog、elicitation_complete、elicitation_response、agent_needs_input、agent_completed、quota_auto_resume_fired、quota_auto_resume_stale、quota_auto_resume_disabled
SubagentStart 代理类型 general-purpose、Explore、Plan、自定义代理名称或插件范围的名称如 ^my-plugin:reviewer$
PreCompact、PostCompact 触发压缩的原因 manual、auto
PreModelSwitch、PostModelSwitch 会话切换到的模型的规范名称,如PreModelSwitch下所述 claude-opus-5、claude-opus-4-6|claude-opus-5、.*opus.*
SubagentStop 代理类型 与 SubagentStart 相同的值
ConfigChange 配置源 user_settings、project_settings、local_settings、policy_settings、skills
CwdChanged 不支持匹配器 总是在每次出现时触发
DirectoryAdded 目录如何被添加 slash_command、register_repo_root
FileChanged 文字文件名以监视(请参阅 FileChanged) .envrc|.env
StopFailure 错误类型 rate_limit、overloaded、authentication_failed、oauth_org_not_allowed、account_on_hold、billing_error、invalid_request、model_not_found、server_error、max_output_tokens、cloud_credential_error、unknown
InstructionsLoaded 加载原因 session_start、nested_traversal、path_glob_match、include、compact
UserPromptExpansion 命令名称 您的 skill 或命令名称
Elicitation MCP 服务器名称 您配置的 MCP 服务器名称
ElicitationResult MCP 服务器名称 与 Elicitation 相同的值
UserPromptSubmit、PostToolBatch、Stop、TeammateIdle、TaskCreated、TaskCompleted、WorktreeCreate、WorktreeRemove、MessageDisplay 不支持匹配器 总是在每次出现时触发

在 cloud_credential_error 上匹配 StopFailure 需要 Claude Code v2.1.267 或更高版本,这是第一个在该值下报告凭证加载失败而不是 server_error 或 unknown 的版本。

对于大多数事件,Claude Code 针对它在 stdin 上发送给您的 hook 的JSON 输入中的字段评估匹配器。对于工具事件,该字段是 tool_name。对于 PreModelSwitch 和 PostModelSwitch,Claude Code 针对它从 to_model 派生的规范名称评估匹配器,如PreModelSwitch下所述。每个hook 事件部分列出了完整的匹配器值集和该事件的输入架构。

此示例仅在 Claude 写入或编辑文件时运行 linting 脚本:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

如果您向不支持匹配器的事件添加 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 开头的工具

精确匹配集中的连字符需要 Claude Code v2.1.195 或更高版本。在早期版本中,像 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 服务器的写入操作:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

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 模型发送提示以进行单轮评估。模型返回其决定作为 JSON。请参阅基于提示的 hooks。
  • 代理 hooks(type: "agent"):生成一个可以使用 Read、Grep 和 Glob 等工具来验证条件的 subagent,然后返回决定。代理 hooks 是实验性的,可能会改变。请参阅基于代理的 hooks。

所有匹配的 hooks 并行运行。如果您在多个设置文件中定义相同的处理程序,它运行一次。插件或 skill 的相同处理程序副本保持分离。

处理程序在当前目录中运行,使用 Claude Code 的环境。如果当前目录不再存在,例如另一个 shell 在会话中途删除的 worktree 或临时目录,Claude Code 从以下第一个仍然存在的目录运行命令 hooks:会话启动的目录、项目根目录、您的主目录或系统临时目录。Claude Code 在调试日志中记录一条警告,命名回退目录。

$CLAUDE_CODE_REMOTE 环境变量在远程 web 环境中为 "true",在本地 CLI 中未设置。Claude Code v2.1.199 及更高版本在本地会话具有活跃的远程控制连接时将$CLAUDE_CODE_BRIDGE_SESSION_ID设置为远程控制会话 ID。

通用字段

这些字段适用于所有 hook 类型:

字段 必需 描述
type 是 "command"、"http"、"mcp_tool"、"prompt" 或 "agent"
if 否 权限规则语法以过滤此 hook 何时运行,例如 "Bash(git *)" 或 "Edit(*.ts)"。hook 命令仅在工具调用与模式匹配时运行。请参阅下面的Bash 匹配表了解 Bash 模式如何针对子命令、$() 和反引号进行评估。仅在工具事件上评估:PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest 和 PermissionDenied。在其他事件上,设置了 if 的 hook 永远不会运行。使用与权限规则相同的语法
timeout 否 取消前的秒数。Claude Code 不在您使用 async: true运行的命令 hook 上强制执行它。默认值:command、http 和 mcp_tool 为 600;prompt 为 30;agent 为 60。Claude Code 在UserPromptSubmit、PreModelSwitch和PostModelSwitch上将 command、http 和 mcp_tool 的默认值降低到 30,在MessageDisplay上降低到 10。SessionEnd hooks 共享 1.5 秒的预算;如果您的设置设置了更长的每个 hook timeout,Claude Code 会提高预算以匹配,最多 60 秒
statusMessage 否 hook 运行时显示的自定义加载程序消息
once 否 如果为 true,Claude Code 在其第一次成功运行后移除 hook。失败、以退出代码 2 阻止或超时的运行会将 hook 保留在原位,因此它在下一个匹配事件上再次运行。仅在skill frontmatter中声明的 hooks 中受尊重;在设置文件和代理 frontmatter 中被忽略

if 字段恰好包含一个权限规则。没有 &&、|| 或列表语法来组合规则;要应用多个条件,请为每个条件定义一个单独的 hook 处理程序。

在文件工具的 if 条件中,单段目录模式如 "Edit(src/**)" 仅匹配工作目录中的 src 目录及其下的文件。要匹配任何深度的名为 src 的目录,请写 "Edit(**/src/**)"。在 v2.1.214 之前,"Edit(src/**)" 匹配工作目录下任何深度的名为 src 的目录。

对于 Bash 模式,您的 hook 命令是否运行取决于模式的形状和 Claude 调用的 Bash 命令。前导 VAR=value 赋值在匹配前被剥离。

if 模式 Bash 命令 Hook 运行? 原因
Bash(git *) FOO=bar git push 是 前导赋值被剥离;git push 匹配
Bash(git *) npm test && git push 是 每个子命令都被检查;git push 匹配
Bash(rm *) echo $(rm -rf /) 是 $() 和反引号内的命令被检查;rm -rf / 匹配
Bash(rm *) echo $(date) 否 没有子命令匹配 rm *
Bash(cat *) echo before $(date) after 否 替换可以位于任何参数位置,因此检查完整命令和 date;都不匹配 cat *
Bash(git *) $TOOL git push 是 Claude Code 无法判断命令名称展开为什么,因此它运行 hook
Bash(git push *) echo $(date) 是 指定超过命令名称的模式在 $()、反引号或 $VAR 上运行 hook

当 Claude Code 无法确定 Bash 输入运行哪些命令时,它无论如何都会运行您的 hook。因为 if 过滤器是尽力而为的,使用权限系统而不是 hook 来强制执行硬允许或拒绝。

命令 hook 字段

除了通用字段外,命令 hooks 还接受这些字段:

字段 必需 描述
command 是 要执行的 shell 命令。与 args 一起,要直接生成的可执行文件。请参阅Exec 形式和 shell 形式
args 否 参数列表。存在时,command 被解析为可执行文件并直接使用 args 作为参数向量生成,不涉及 shell。请参阅Exec 形式和 shell 形式
async 否 如果为 true,在后台运行而不阻止。请参阅在后台运行 hooks
asyncRewake 否 如果为 true,在后台运行并在退出代码 2 时唤醒 Claude。hook 的 stderr,或 stdout(如果 stderr 为空),作为系统提醒显示给 Claude,以便它可以对长时间运行的后台失败做出反应
shell 否 用于此 hook 的 shell。接受 "bash" 或 "powershell"。默认为 "bash",或在未安装 Git Bash 时在 Windows 上默认为 "powershell"。设置 "powershell" 在 Windows 上通过 PowerShell 运行命令。不需要 CLAUDE_CODE_USE_POWERSHELL_TOOL,因为 hooks 直接生成 PowerShell。设置 args 时被忽略
Exec 形式和 shell 形式

当设置 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。

此示例运行与插件捆绑的 Node 脚本。Exec 形式将解析的脚本路径作为一个参数传递,不带引号:

{
  "type": "command",
  "command": "node",
  "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}

等效的 shell 形式需要引号来处理包含空格或特殊字符的路径:

{
  "type": "command",
  "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}

两种形式都支持相同的路径占位符,并且都将它们作为环境变量 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.*}。

HTTP hook 字段

除了通用字段外,HTTP hooks 还接受这些字段:

字段 必需 描述
url 是 发送 POST 请求的 URL
headers 否 其他 HTTP 标头作为键值对。值支持使用 $VAR_NAME 或 ${VAR_NAME} 语法的环境变量插值。仅解析 allowedEnvVars 中列出的变量
allowedEnvVars 否 可能被插值到标头值中的环境变量名称列表。对未列出变量的引用被替换为空字符串。任何环境变量插值都需要此项

Claude Code 使用 Content-Type: application/json 将 hook 的JSON 输入作为 POST 请求体发送。响应体使用与命令 hooks 相同的JSON 输出格式。

错误处理与命令 hooks 不同;请参阅HTTP 响应处理。

此示例将 PreToolUse 事件发送到本地验证服务,使用来自 MY_TOKEN 环境变量的令牌进行身份验证:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

MCP 工具 hook 字段

除了通用字段外,MCP 工具 hooks 还接受这些字段:

字段 必需 描述
server 是 已配置的 MCP 服务器的名称。对于插件捆绑的服务器,这是作用域名称 plugin:<plugin-name>:<server-name>,例如 plugin:my-plugin:db,而不是裸服务器密钥。服务器必须已连接;hook 永远不会触发 OAuth 或连接流
tool 是 该服务器上要调用的工具的名称
input 否 传递给工具的参数。字符串值支持从 hook 的JSON 输入进行 ${path} 替换,例如 "${tool_input.file_path}"

Claude Code 读取工具的文本内容的方式与读取命令 hook stdout 相同,遵循退出代码 0 下的解析规则。如果命名的服务器未连接,或工具返回 isError: true,hook 会产生非阻止错误,执行继续。

此示例在每个 Write 或 Edit 后在 my_server MCP 服务器上调用 security_scan 工具,传递编辑文件的路径:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "security_scan",
            "input": { "file_path": "${tool_input.file_path}" }
          }
        ]
      }
    ]
  }
}

mcp_tool hook 仅在 Claude Code 使会话的 MCP 服务器对 hooks 可用后才能在每个 hook 事件上运行。SessionStart 和 Setup 可能在该点之前触发:

  • 在启动时:SessionStart 在服务器可用之前触发,包括当您使用 --continue 或 --resume 启动时。Claude Code 跳过事件的 mcp_tool hooks 而不调用它们的工具,调试日志记录 mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context)。
  • 稍后在运行的会话中:在 /clear 或压缩后,SessionStart 再次触发,服务器已可用,其 mcp_tool hooks 运行。
  • 在 Setup 上:Setup 总是在服务器可用之前触发,因此 Claude Code 每次都跳过其 mcp_tool hooks 并记录相同的消息,命名 Setup。

例如,此配置从没有匹配器的 SessionStart hook 在 my_server MCP 服务器上调用 load_context 工具,因此它适用于每个 SessionStart 源:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "load_context"
          }
        ]
      }
    ]
  }
}

当您运行 claude 时,Claude Code 跳过此 hook,永远不调用 load_context,并将 no MCP client context 消息写入调试日志。在该同一会话中运行 /clear,hook 运行并调用 load_context。type: "command" hook 在 SessionStart 上运行,因此对会话从其第一个转向需要的任何东西使用一个。

提示和代理 hook 字段

除了通用字段外,提示和代理 hooks 还接受这些字段:

字段 必需 描述
prompt 是 要发送给模型的提示文本。使用 $ARGUMENTS 作为 hook 输入 JSON 的占位符。使用反斜杠转义以包含文字文本:\$1.00 呈现为 $1.00
model 否 用于评估的模型。默认为快速模型

按路径引用脚本

使用这些占位符按项目或插件根目录引用 hook 脚本,无论 hook 运行时的工作目录如何:

  • ${CLAUDE_PROJECT_DIR}:项目根目录,会话启动的位置。Claude Code 也在stdio MCP 服务器和插件 LSP 服务器的环境中设置此变量。
  • ${CLAUDE_PLUGIN_ROOT}:插件的安装目录,用于与插件捆绑的脚本。请参阅插件环境变量了解路径在更新中的行为。
  • ${CLAUDE_PLUGIN_DATA}:插件的持久数据目录,用于应该在插件更新后保留的依赖项和状态。

对于任何引用路径占位符的 hook,优先使用exec 形式。在 shell 形式中,用双引号包装每个占位符。

此示例使用 ${CLAUDE_PROJECT_DIR} 在任何 Write 或 Edit 工具调用后从项目的 .claude/hooks/ 目录运行样式检查器:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}

Skills 和代理中的 Hooks

除了设置文件和插件外,hooks 还可以使用 frontmatter 直接在skills和subagents中定义,使用与基于设置的 hooks 相同的配置格式。Claude Code 保持它们注册多长时间取决于组件:

  • Subagent hooks:Claude Code 仅在该 subagent 运行时运行它们,并在其完成时移除它们。Claude Code 在此处将 Stop hook 转换为 SubagentStop,这是 subagent 完成时触发的事件。
  • Skill hooks:Claude Code 在您或 Claude 调用 skill 时注册它们,并在会话的其余部分保持运行它们,在 skill 自己的转向之后的转向上也是如此。要让 Claude Code 在第一次成功运行后移除 hook,请在其上设置once: true。

此 skill 定义了一个 PreToolUse hook,在每个 Bash 命令之前运行安全验证脚本:

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

Subagents 在其 YAML frontmatter 中使用相同的格式。

项目 skill 中的 Frontmatter hooks 遵循与设置文件中的 hooks 相同的工作区信任规则。Claude Code 在您或 Claude 调用 skill 时注册它们,包括在您未信任的文件夹中的 -p 运行。

项目 subagent 中的 Frontmatter hooks 仅在您接受 agent 文件来自的文件夹的工作区信任对话后运行。-p 会话不计为接受它。在您信任文件夹之前运行的内容将此与设置文件规则进行比较,subagents 页面列出哪些范围是豁免的。在 v2.1.218 之前,这些 hooks 可以从您未信任的文件夹运行。

`/hooks` 菜单

在 Claude Code 中键入 /hooks 以打开您配置的 hooks 的只读浏览器。菜单显示每个 hook 事件及其配置的 hooks 计数,让您深入了解匹配器,并显示每个 hook 处理程序的完整详细信息。使用它来验证配置、检查 hook 来自哪个设置文件,或检查 hook 的命令、提示或 URL。

菜单显示所有五种 hook 类型:command、prompt、agent、http 和 mcp_tool。每个 hook 都标有 [type] 前缀和指示其定义位置的源:

  • User Settings:来自 ~/.claude/settings.json
  • Project Settings:来自 .claude/settings.json
  • Local Settings:来自 .claude/settings.local.json
  • Plugin Hooks:来自插件的 hooks/hooks.json
  • Session Hooks:在当前会话中在内存中注册

选择 hook 会打开详细视图,显示其事件、匹配器、类型、源文件以及完整的命令、提示或 URL。菜单是只读的:要添加、修改或移除 hooks,请直接编辑设置 JSON 或要求 Claude 进行更改。

禁用或移除 hooks

要移除 hook,请从设置 JSON 文件中删除其条目。

要临时禁用所有 hooks 而不移除它们,请在设置文件中设置 "disableAllHooks": true。Claude Code 读取设置优先级应用后留下的值,因此项目的 .claude/settings.json 中的 "disableAllHooks": false 覆盖您的用户设置中的 true。要关闭一次运行,无论项目的设置说什么,请传递 --settings '{"disableAllHooks": true}',这优先于项目和本地设置。没有办法在保持 hook 在配置中的同时禁用单个 hook。

disableAllHooks 设置遵守托管设置层次结构。如果管理员通过托管策略设置配置了 hooks,则在用户、项目或本地设置中设置的 disableAllHooks 无法禁用这些托管 hooks。仅在托管设置级别设置的 disableAllHooks 可以禁用托管 hooks。对于每个级别的完整范围,请参阅disableAllHooks。

对设置文件中 hooks 的直接编辑通常由文件监视程序自动拾取。

Hook 输入和输出

命令 hook 通过 stdin 接收 JSON 数据,并通过退出代码、stdout 和 stderr 传达结果。HTTP hook 接收与 POST 请求体相同的 JSON,并通过 HTTP 响应体传达结果。本节涵盖所有事件通用的字段和行为。Hook 事件下的每个事件部分包括其特定的输入架构和决策控制选项。

在 macOS 和 Linux 上,命令 hook 在没有控制终端的自己的会话中运行。hook 进程和任何子进程无法打开 /dev/tty 或直接向 Claude Code 界面发送转义序列。Windows 没有 /dev/tty。

要在任何平台上向用户显示消息,请在 JSON 输出中返回 systemMessage。某些事件会丢弃它或将其传递到其他地方,每个事件部分都会说明这一点。要触发桌面通知、设置窗口标题或响铃,请改为返回 terminalSequence。

通用输入字段

Hook 事件接收这些字段作为 JSON,除了每个 hook 事件部分中记录的事件特定字段。对于命令 hook,此 JSON 通过 stdin 到达。对于 HTTP hook,它作为 POST 请求体到达。

字段 描述
session_id 当前会话标识符
prompt_id 标识当前正在处理的用户提示的 UUID。与 OpenTelemetry 事件上的 prompt.id 属性匹配,因此您可以将 hook 输出与单个提示的遥测关联起来。在第一个用户输入之前不存在。需要 Claude Code v2.1.196 或更高版本
transcript_path 对话 JSON 的路径。转录文件异步写入,可能滞后于内存中的对话,因此当 hook 触发时,它可能还不包括当前轮次的最新消息。需要当前轮次最终助手文本的 hook 应在 Stop 和 SubagentStop 上使用 last_assistant_message,而不是读取转录
cwd 调用 hook 时的当前工作目录
scratchpad_dir 会话的 scratchpad 目录的路径,Claude 在其中保存临时工作文件。当会话没有 scratchpad 或 temp 目录不可用时不存在。需要 Claude Code v2.1.257 或更高版本
permission_mode 当前权限模式:"default"、"plan"、"acceptEdits"、"auto"、"dontAsk" 或 "bypassPermissions"。标记为手动的模式作为 "default" 到达,从不作为 "manual",因此匹配 "default" 的脚本继续工作。并非所有事件都接收此字段。检查每个 hook 事件部分中的 JSON 示例
effort 对象,其 level 字段保存 hook 运行时生效的工作量级别:"low"、"medium"、"high"、"xhigh" 或 "max"。如果您设置了活跃模型不支持的级别,level 会报告 Claude Code 运行的级别;调整工作量级别说明它如何选择该级别。Ultracode 不是一个不同的级别,报告为 "xhigh"。该对象与状态行 effort 字段匹配。对于在工具使用上下文中触发的事件(如 PreToolUse、PostToolUse、Stop 和 SubagentStop),当当前模型支持工作量参数时存在。该级别也可作为 $CLAUDE_EFFORT 环境变量供 hook 命令和 Bash 工具使用。
hook_event_name 触发的事件的名称

使用 --agent 运行或在 subagent 内部时,包括两个额外字段:

字段 描述
agent_id subagent 的唯一标识符。仅当 hook 在 subagent 调用内触发时存在。使用此来区分 subagent hook 调用和主线程调用。
agent_type Agent 名称(例如,"Explore" 或 "security-reviewer")。当会话使用 --agent 或 hook 在 subagent 内触发时存在。对于 subagent,subagent 的类型优先于会话的 --agent 值。请参阅 SubagentStart 了解自定义和插件 subagent 报告的值以及如何针对插件范围的名称编写匹配器。

只有 SessionStart hook 可以接收 model 字段,Claude Code 并不总是包括它。PreModelSwitch 和 PostModelSwitch hook 改为接收 from_model 和 to_model,因此使用 PostModelSwitch hook 来跟踪模型在会话期间的变化。

没有 $CLAUDE_MODEL 环境变量。如果您在 shell 中设置了 hook,可以读取 $ANTHROPIC_MODEL,但该值在您使用 /model 在会话期间切换模型时不会改变。

hook 进程继承父环境,除了 Claude Code 从它生成的每个子进程中删除的 OTEL_* 导出器变量,以及当 CLAUDE_CODE_SUBPROCESS_ENV_SCRUB 设置为 1 时它剥离的变量。

例如,Bash 命令的 PreToolUse hook 在 stdin 上接收以下内容:

{
  "session_id": "abc123",
  "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite",
    "timeout": 120000,
    "run_in_background": false
  },
  "tool_use_id": "toolu_01ABC123..."
}

tool_name、tool_input 和 tool_use_id 字段是事件特定的。每个 hook 事件部分记录该事件的额外字段。

退出代码输出

来自 hook 命令的退出代码告诉 Claude Code 该操作是否应继续、被阻止或被忽略。退出代码不单独起作用。Claude Code 从 stdout 读取JSON 输出字段,无论退出代码是什么,对于使用标准决策模型的事件,通过架构验证的解析对象与代码一起生效。退出 2 的阻止是 JSON 无法覆盖的唯一结果。

两个表拥有每个事件的例外:每个事件的退出代码 2 行为说明退出代码对每个事件的作用,决策控制说明每个事件接受哪些决策字段。通用字段如 systemMessage 在大多数事件中工作,并在 JSON 输出表中列出。

退出代码 0

退出 0 表示成功,是您打印 JSON 进行结构化控制时的预期退出代码。

对于大多数事件,Claude Code 将 stdout 写入调试日志,不在转录中显示。例外是 UserPromptSubmit、UserPromptExpansion、SessionStart 和 PostModelSwitch,其中 Claude Code 添加纯文本 stdout 作为 Claude 可以看到和作用的上下文。

Claude Code 是否将您的 stdout 读取为 JSON 输出或纯文本取决于它如何开始和结束,忽略周围的空格:

  • 以 { 开始并以 } 结束:Claude Code 将其解析为 JSON。当输出是两行或更多行,每行本身都解析为 JSON,且没有行是设置字段的 JSON 输出对象时,Claude Code 将整个输出视为纯文本。当其中一行确实设置了字段时,整个输出是解析失败,如下所述。
  • 以 { 开始但不以 } 结束:Claude Code 将其视为纯文本。
  • 以其他任何内容开始:Claude Code 将其视为纯文本、JSON 数组或包含的引用 JSON 字符串。

对于使用标准决策模型的事件,退出 0 且解析对象未通过架构验证是非阻止错误:操作继续,转录显示 <hook name> hook error 通知,带有验证消息。在任何退出代码(除 2 外)上都会发生相同情况,而退出 2 仍然阻止。

对于使用标准决策模型的事件,当 Claude Code 尝试将您的 stdout 解析为 JSON 且无法解析时,它在除 2 外的每个退出代码上报告非阻止错误。转录显示 <hook name> hook error 通知,带有解析消息。在添加纯文本 stdout 作为上下文的事件上,Claude Code 不添加文本。在 v2.1.248 之前,Claude Code 将该 stdout 视为纯文本。

来自退出 0 的 hook 的 Stderr 仅进入调试日志,从不进入转录,Claude 从不看到它。要自己读取它,请启用调试日志。要从 PostToolUse 或 PostToolUseFailure hook 向 Claude 显示警告,请改为退出 2,以便Claude 看到 stderr,即使工具已经运行。

退出代码 2

退出 2 表示阻止错误。在可以阻止的事件上,退出 2 无论您是否打印 JSON 都会阻止:即使 JSON permissionDecision 为 "allow" 也无法覆盖它。Claude Code 仍然读取 stdout 上的任何有效 JSON 输出。在 Elicitation 和 ElicitationResult 上,退出 2 hook 的 hookSpecificOutput 被忽略。

阻止消息是您的 JSON 的阻止决策的原因(当它做出一个时),否则是您的 stderr 文本。阻止做什么因事件而异:PreToolUse 阻止工具调用,UserPromptSubmit 拒绝提示,等等。每个事件的退出代码 2 行为列出每个事件的效果,每个事件的部分说明消息去向。

退出 2 的 hook 同时打印 JSON 且未通过 JSON 输出架构验证仍然阻止:Claude Code 使用 stderr 作为阻止原因,并在调试日志中记录验证失败。在 v2.1.214 之前,Claude Code 将该组合视为非阻止错误,操作继续。

此脚本通过退出 2 阻止 rm 命令,并将所有其他命令留给正常权限流:

#!/bin/bash
# Reads JSON input from stdin, checks the command
input=$(cat)
command=$(jq -r '.tool_input.command' <<<"$input")

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # Blocking error: tool call is prevented
fi

exit 0  # No decision: the normal permission flow applies

其他退出代码

任何其他退出代码对于大多数 hook 事件本身不会阻止。发生什么取决于您的 stdout:

  • 使用通过架构验证的解析对象,对于使用标准决策模型的事件,Claude Code 忽略退出代码,JSON 单独决定结果:
    • 事件支持的每个字段都被接受,包括 permissionDecision、additionalContext、updatedInput 和 systemMessage,hook 不被报告为错误。
    • 决策控制列出每个事件的决策字段;通用字段如 systemMessage 遵循 JSON 输出表。
  • 使用未通过架构验证的解析对象,对于使用标准决策模型的事件,它与退出 0 上相同的非阻止错误:操作继续,<hook name> hook error 通知带有验证消息。
  • 使用 Claude Code 尝试解析为 JSON且无法解析的 stdout,Claude Code 报告与退出 0 上相同的非阻止错误,用于使用标准决策模型的事件。操作继续,通知带有解析消息。
  • 使用 Claude Code 视为纯文本的 stdout,或使用空 stdout,对于大多数 hook 事件是非阻止错误:操作继续,转录显示 <hook name> hook error 通知,后跟 stderr 的第一行,前缀为 Failed with non-blocking status code:。要捕获完整 stderr,请启用调试日志。

标准决策模型之外的事件在每个事件表中保持自己的行:WorktreeCreate 在任何非零退出时失败创建,无论您的 JSON 说什么,事件丢弃 hook 输出(如 StopFailure)在每个退出代码上忽略您的 JSON,除了副作用字段如 terminalSequence,它仍然触发。

无法启动的 hook 落入相同的非阻止桶。当脚本路径不存在或不可执行时,shell 以代码(如 127)退出,您看到相同的通知,带有解释器的消息,例如 Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory。对于大多数 hook 事件,操作继续。当您设置策略 hook 时,在其第一次运行时观察此通知:settings.json 中的拼写错误的路径使门无声地禁用。

超时

除了您使用 async: true 运行的命令 hook,Claude Code 取消达到其 timeout 的 command、http 或 mcp_tool hook,丢弃 hook 的输出,因此在大多数事件上超时的 hook 不呈现决策。

在 PreModelSwitch 上,在其超时处取消的 hook 阻止模型切换。在 PreToolUse 上,两个 hook 系列不同:

每个事件的退出代码 2 行为

退出代码 2 是 hook 发出"停止,不要这样做"信号的方式。效果取决于事件,因为某些事件代表可以被阻止的操作(如尚未发生的工具调用),而其他事件代表已经发生或无法防止的事情。

Hook 事件 可以阻止? 退出 2 时发生什么
PreToolUse 是 阻止工具调用
PermissionRequest 否 此事件不接受退出代码 2,权限流程保持不变。改为通过 decision 对象拒绝
UserPromptSubmit 是 阻止提示处理并删除提示
UserPromptExpansion 是 阻止扩展
Stop 是 防止 Claude 停止,继续对话
SubagentStop 是 防止 subagent 停止
TeammateIdle 是 防止队友空闲,因此它继续工作
TaskCreated 是 回滚任务创建
TaskCompleted 是 防止任务被标记为已完成
ConfigChange 是 阻止配置更改生效(除 policy_settings 外)
StopFailure 否 输出和退出代码被忽略,除 terminalSequence 外
PostToolUse 否 向 Claude 显示 stderr;工具已经运行
PostToolUseFailure 否 向 Claude 显示 stderr;工具已经失败
PostToolBatch 是 在下一个模型调用之前停止代理循环
PermissionDenied 否 退出代码和 stderr 被忽略,因为拒绝已经发生。使用 JSON hookSpecificOutput.retry: true 告诉模型它可能重试;Claude Code 对无判决拒绝忽略 retry: true
Notification 否 退出代码和 stderr 被忽略
SubagentStart 否 仅向用户显示 stderr
SessionStart 否 仅向用户显示 stderr
Setup 否 退出代码和 stderr 被忽略
SessionEnd 否 仅向用户显示 stderr
CwdChanged 否 仅向用户显示 stderr
DirectoryAdded 否 Stderr 进入调试日志;目录已经添加
FileChanged 否 仅向用户显示 stderr
PreCompact 是 阻止压缩
PostCompact 否 仅向用户显示 stderr
PreModelSwitch 是 阻止模型切换并向用户显示 stderr
PostModelSwitch 否 仅向用户显示 stderr;模型已经切换
Elicitation 是 拒绝引出
ElicitationResult 是 阻止响应(操作变为拒绝)
WorktreeCreate 是 任何非零退出代码导致 worktree 创建失败
WorktreeRemove 是 任何非零退出代码导致 worktree 移除失败(如果目录仍然存在)。请参阅 WorktreeRemove 了解目录发生什么
InstructionsLoaded 否 退出代码被忽略
MessageDisplay 否 显示原始文本

对于 SessionStart、SubagentStart 和 PostModelSwitch,Claude Code 在转录中呈现退出代码 2 stderr 作为 <hook name> hook error 通知,与呈现非阻止错误的方式相同。Claude 看不到它,会话或 subagent 继续。对于 SubagentStart,通知出现在 subagent 自己的转录中,而不是在父对话中。

HTTP 响应处理

HTTP hook 使用 HTTP 状态代码和响应体而不是退出代码和 stdout。下面的结果适用于大多数事件;在每个事件表中有自己的失败合约的事件(如 WorktreeCreate)将该合约应用于失败的 HTTP hook:

  • 2xx 且空体:成功,等同于退出代码 0 且无输出
  • 2xx 且 JSON 对象体:使用与命令 hook 相同的 JSON 输出架构解析。未通过架构验证的体是非阻止错误
  • 2xx 且任何其他体,如纯文本:非阻止错误,处理方式与非 2xx 状态相同。Claude Code 不将文本添加到 Claude 的上下文
  • 非 2xx 状态:非阻止错误,执行继续
  • 连接失败:非阻止错误,执行继续
  • 超时:hook 被取消,如 Timeouts 下所述

与命令 hook 不同,HTTP hook 无法仅通过状态代码发出阻止错误信号。要阻止工具调用或拒绝权限,返回 2xx 响应,其 JSON 体包含适当的决策字段。

JSON 输出

退出代码只让您阻止或保持沉默,但 JSON 输出给您更细粒度的控制。与其退出代码 2 来阻止,不如退出 0 并将 JSON 对象打印到 stdout。Claude Code 从该 JSON 读取特定字段来控制行为,包括决策控制来阻止、允许或升级给用户。

您的 hook 的 stdout 必须仅包含 JSON 对象。如果您的 shell 配置文件在启动时打印文本,它可能会干扰 JSON 解析。请参阅故障排除指南中的 Hook JSON 无效。

hook 的 additionalContext、systemMessage 和 initialUserMessage 字符串,以及其纯 stdout,限制为 10,000 个字符:

  • 范围:Claude Code 单独测量每个字符串,即使多个 hook 为同一事件运行。对于 JSON 输出,每个字段单独测量;纯 stdout 整体测量。
  • 超过限制:Claude Code 将输出保存到会话目录中的文件,并用文件路径和最多前 2,000 个字符的预览替换它。大型有效 Bash 结果的处理方式相同,在 Output limits 下描述。与该 Bash 上限不同,此上限没有设置或环境变量来提高它。
  • 读取文件:Claude Code 不要求 Claude 读取文件,因此将 Claude 必须始终看到的任何内容保持在上限内。

JSON 对象支持三种字段:

  • 通用字段如 continue 在下表中列出。每个事件都接受它们,但某些事件丢弃它们或将 systemMessage 传递到转录以外的地方。每个事件的部分说明这一点。terminalSequence 也在这些事件上工作,除了 Emit terminal notifications 下列出的例外。
  • 顶级 decision 和 reason 由某些事件用来阻止或提供反馈。
  • hookSpecificOutput 是需要更丰富控制的事件的嵌套对象。它需要一个 hookEventName 字段设置为事件名称。
字段 默认 描述
continue true 如果 false,Claude 在 hook 运行后完全停止处理。优先于任何事件特定的决策字段
stopReason 无 当 continue 为 false 时向用户显示的消息。它保留在对话中,因此如果对话继续,Claude 会看到它
suppressOutput false 无效果:Claude Code 接受字段但不作用。成功的 hook 的 stdout 从不在转录中显示,并在调试日志中记录
systemMessage 无 向用户显示的警告消息。在 Agent SDK 和 --output-format stream-json 输出中,它可以作为 SDKInformationalMessage 到达
terminalSequence 无 Claude Code 代表您发出的终端转义序列,如桌面通知、窗口标题或响铃。限制为 OSC 0/1/2/9/99/777 和 BEL。如果值包含允许列表外的任何内容,字段被忽略。使用此而不是写入 /dev/tty,这对 hook 不可用

要完全停止 Claude:

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

对于 PreToolUse 和 PostToolUse hook,停止适用,即使工具调用失败或在 Claude 仍在流式传输响应时完成。

发出终端通知

Hook 运行时没有控制终端,因此直接写入转义序列到 /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

序列可以用 BEL 或 ST 终止。允许列表外的任何内容,包括 CSI 光标和颜色序列、OSC 调色板序列、OSC 8 超链接、OSC 52 剪贴板写入和 OSC 1337,被拒绝,字段被忽略。

Claude Code 在处理您的 hook 输出时写入序列本身,因此字段在丢弃 systemMessage 和 continue 的事件上工作,如 Notification 和 StopFailure。它有两个限制:

  • Claude Code 仅在交互式会话中写入序列,仅当其界面在屏幕上时。在使用 -p 标志的非交互式模式和 Agent SDK 中,它忽略字段。
  • WorktreeCreate 命令 hook 无法返回 JSON,因为 Claude Code 将其 stdout 读取为 worktree 路径。HTTP WorktreeCreate hook 返回 JSON 并可以包括字段。

下面的示例从 Notification hook 触发桌面通知。转义序列用 printf 八进制转义构建,因此控制字节从不出现在 shell 命令行上,jq -n --arg 构建 JSON 输出,因此通知消息中的引号、反斜杠和换行符被正确转义:

#!/bin/bash
# Notification hook: ping the desktop when Claude Code needs attention.
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

{ "terminalSequence": "..." } 形状从任何 shell 或语言都相同。

为 Claude 添加上下文

additionalContext 字段将字符串从您的 hook 传递到 Claude 的上下文窗口。Claude Code 将字符串包装在系统提醒中,并在 hook 触发的点将其插入对话。Claude 在下一个模型请求时读取提醒,但它不作为聊天消息出现在界面中。

在 hookSpecificOutput 中返回 additionalContext 以及事件名称:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
  }
}

提醒出现的位置取决于事件:

当多个 hook 为同一事件返回 additionalContext 时,Claude 接收所有值。

如果值超过 10,000 个字符,Claude Code 将文本写入会话目录中的文件,并改为传递 Claude 文件路径,带有最多前 2,000 个字符的预览。Claude 可以读取文件,但 Claude Code 不要求它。

使用 additionalContext 获取 Claude 应该知道的关于您的环境当前状态或刚刚运行的操作的信息:

  • 环境状态:当前分支、部署目标或活跃功能标志
  • 条件项目规则:哪个测试命令适用于刚编辑的文件,哪些目录在此 worktree 中是只读的
  • 外部数据:分配给您的开放问题、最近的 CI 结果、从内部服务获取的内容

对于从不改变的说明,更喜欢 CLAUDE.md。它加载而不运行脚本,是静态项目约定的标准位置。

将文本写成事实陈述而不是命令式系统说明。措辞如"部署目标是生产"或"此 repo 使用 bun test"读作项目信息。框架为带外系统命令的文本可以触发 Claude 的提示注入防御,这导致 Claude 向您显示文本而不是将其视为上下文。

Claude Code 在会话转录中保存注入的文本。对于 PostToolUse 或 UserPromptSubmit 等中期会话事件,当您使用 --continue 或 --resume 恢复时,Claude Code 重放保存的文本而不是为过去的轮次重新运行 hook,因此时间戳或提交 SHA 等值变得陈旧。SessionStart hook 在使用 source 设置为 "resume" 或 "fork"(如果您添加了 --fork-session)恢复时再次运行,因此它们可以刷新其上下文。

决策控制

并非每个事件都支持通过 JSON 阻止或控制行为。支持的事件各自使用不同的字段集来表达该决策。在编写 hook 之前,使用此表作为快速参考:

事件 决策模式 关键字段
UserPromptSubmit、UserPromptExpansion、PostToolUse、PostToolUseFailure、PostToolBatch、Stop、SubagentStop、ConfigChange、PreCompact 顶级 decision decision: "block"、reason。Stop 和 SubagentStop 也接受 hookSpecificOutput.additionalContext 用于继续对话的非错误反馈
TeammateIdle、TaskCompleted 退出代码或 continue: false 退出代码 2 用 stderr 反馈阻止操作。JSON {"continue": false, "stopReason": "..."} 也完全停止队友,匹配 Stop hook 行为;TaskCompleted 在 TaskUpdate 工具触发事件时忽略它
TaskCreated 退出代码或顶级 decision 退出代码 2 或 decision: "block" 取消任务并将消息返回给 Claude。continue: false 被忽略
PreToolUse hookSpecificOutput permissionDecision(allow/deny/ask/defer)、permissionDecisionReason
PreModelSwitch hookSpecificOutput 或顶级 decision permissionDecision(allow/deny/ask)、permissionDecisionReason。decision: "block" 也取消切换
PermissionRequest hookSpecificOutput decision.behavior(allow/deny)
PermissionDenied hookSpecificOutput retry: true 告诉模型它可能重试被拒绝的工具调用;Claude Code 对无判决拒绝忽略它
WorktreeCreate 路径返回 命令 hook 在 stdout 上打印路径;HTTP hook 返回 hookSpecificOutput.worktreePath。Hook 失败或缺少路径失败创建
WorktreeRemove 退出代码 任何非零退出代码使移除失败(如果目录仍然存在)。JSON 输出被丢弃
Elicitation hookSpecificOutput action(accept/decline/cancel)、content(接受的表单字段值)
ElicitationResult hookSpecificOutput action(accept/decline/cancel)、content(表单字段值覆盖)
MessageDisplay hookSpecificOutput displayContent 替换屏幕上显示的文本。仅显示:转录和 Claude 看到的保持原始
SessionStart、SubagentStart、PostModelSwitch 仅上下文 hookSpecificOutput.additionalContext 为 Claude 添加上下文。SessionStart 也接受 initialUserMessage、watchPaths、sessionTitle 和 reloadSkills。无阻止或决策控制
Setup、Notification、SessionEnd、PostCompact、InstructionsLoaded、StopFailure、CwdChanged、DirectoryAdded、FileChanged 无 无决策控制。用于日志或清理等副作用

少数事件也可以重写内容而不仅仅允许或阻止它:

对于编辑或转换用例,在 PreToolUse 处拦截出站工具输入,在 PostToolUse 处拦截入站工具结果。

以下是每种模式的实际示例:

decision 的唯一值是 "block"。要允许操作继续,从您的 JSON 中省略 decision,或退出 0 而不带任何 JSON:

{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}

有关扩展示例,包括 Bash 命令验证、提示过滤和自动批准脚本,请参阅指南中的 What you can automate 和 Bash command validator reference implementation。

Hook 事件

每个事件对应于 Claude Code 生命周期中的一个点,hooks 可以在该点运行。下面的部分按照生命周期顺序排列:从会话设置到 agentic 循环再到会话结束。每个部分描述事件何时触发、它支持哪些匹配器、它接收的 JSON 输入,以及如何通过输出控制行为。

SessionStart

在 Claude Code 启动新会话或恢复现有会话时运行。对于加载开发上下文(如现有问题或代码库的最近更改)或设置环境变量很有用。对于不需要脚本的静态上下文,请改用 CLAUDE.md。

SessionStart 在每个会话上运行,因此请保持这些 hooks 快速。仅支持 type: "command" 和 type: "mcp_tool" hooks。有关 mcp_tool hooks 何时运行,请参阅 MCP tool hook 字段。

匹配器值对应于会话的启动方式:

匹配器 何时触发
startup 新会话
resume --resume、--continue 或 /resume
clear /clear
compact 自动或手动压缩
fork 从现有会话分叉的新会话:--fork-session 与 --resume 或 --continue、/fork 后台副本或 /branch

在 v2.1.214 之前,分叉的会话报告源为 "resume"。

当您启动交互式会话、使用 --continue 或 --resume 在启动时恢复对话、或运行 /clear 时,SessionStart hooks 在后台运行。您可以立即输入,恢复的对话显示时无需等待 hooks。Claude 的第一个响应仍然等待 hooks 完成,因此它们的上下文到达 Claude。

当您在会话内使用 /resume 切换对话时,切换等待 hooks 完成。如果您在后台 hooks 仍在运行时运行 /clear 或切换到另一个对话,它们返回的任何内容都不适用于会话。

在启动时也适用相同的等待,包括恢复的会话:您在 SessionStart hooks 仍在运行时发送的提示不会到达 Claude,直到它们完成。

在任一等待期间,按 Esc 将提示返回到输入中而不发送它。hooks 继续运行。

SessionStart 输入

除了 常见输入字段 外,SessionStart hooks 还接收 source 和可选的 model、agent_type 和 session_title:

字段 描述
source 会话如何启动:新会话为 "startup"、恢复的会话为 "resume"、/clear 后为 "clear"、压缩后为 "compact",或从现有会话分叉的新会话为 "fork"
model 活跃的模型标识符。它可以被省略,例如在 /clear 后或通过对话恢复恢复会话时,因此在读取它之前检查该字段
agent_type agent 名称,当您使用 claude --agent <name> 启动 Claude Code 时出现
session_title 当前会话标题(如果已设置),例如通过 --name 或 /rename。一个发出 sessionTitle 的 hook 可以先检查 session_title 以避免覆盖用户明确设置的标题

当 source 为 "resume" 或 "fork" 且成绩单包含至少一个来自 Claude 的响应时,SessionStart hooks 还接收下面的四个字段。您的 hook 可以使用它们在第一个请求之前报告恢复陈旧对话的成本,例如在 systemMessage 中。这些字段需要 Claude Code v2.1.251 或更高版本。

字段 描述
seconds_since_last_response 自恢复成绩单中最后一个响应以来的挂钟秒数
context_tokens 恢复会话的第一个请求作为其提示重新发送的令牌
prompt_cache_likely_expired 当最后一个响应早于会话的 prompt cache 生命周期 或更晚的压缩替换了缓存的对话时为 true
estimated_cache_write_usd 将 context_tokens 写入会话模型的 prompt cache 的估计成本(美元),不包括响应

此示例显示了在最后一个响应后 90 分钟恢复的会话的输入:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "resume",
  "model": "claude-opus-5",
  "seconds_since_last_response": 5400,
  "context_tokens": 182340,
  "prompt_cache_likely_expired": true,
  "estimated_cache_write_usd": 1.1396
}

SessionStart 决策控制

Claude Code 将它 视为纯文本 的 stdout 添加到 Claude 的上下文中。除了所有 hooks 可用的 JSON 输出字段 外,您还可以返回这些事件特定的字段:

字段 描述
additionalContext 在对话开始时添加到 Claude 上下文的字符串,在第一个提示之前。有关文本如何传递以及放入其中的内容,请参阅 为 Claude 添加上下文
initialUserMessage 用作会话第一个用户消息的字符串。适用于 非交互模式,带有 -p 标志,即使未提供提示,它也成为第一个回合。如果提供了提示,它作为下一个回合跟随。与 additionalContext 不同,后者附加到现有回合,这会创建回合
sessionTitle 设置会话标题,与 /rename 效果相同。用于从启动文件夹、git 分支或 worktree 名称自动命名会话。当 source 为 "startup"、"resume" 或 "fork" 时适用;在 "clear" 和 "compact" 上被忽略
watchPaths 要在此会话期间监视 FileChanged 事件的绝对路径数组
reloadSkills 布尔值。当为 true 时,Claude Code 在 SessionStart hooks 完成后重新扫描 skill 和命令目录,因此 hook 安装的 skills 在同一会话中可用,从第一个提示开始
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
    "sessionTitle": "auth-refactor"
  }
}

由于纯 stdout 已经为此事件到达 Claude,仅加载上下文的 hook 可以直接打印到 stdout,而无需构建 JSON。当您需要将上下文与其他字段(如 sessionTitle)结合时,使用 JSON 形式。

当 SessionStart hook 安装或更新 skills 时使用 reloadSkills。Skill 发现通常在 SessionStart hooks 完成之前运行,因此 hook 写入 ~/.claude/skills/ 或 .claude/skills/ 的文件否则只会在下一个会话中出现。此示例同步共享 skills 存储库并请求重新扫描:

#!/bin/bash

git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
  git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills

echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'

存储库 URL 是占位符;将其替换为您自己的 skills 存储库。使用占位符,克隆失败并打印 fatal: 消息到 stderr。来自退出 0 的 SessionStart hook 的 stderr 仅供参考,因此 reloadSkills 请求仍然适用。

持久化环境变量

SessionStart hooks 可以访问 CLAUDE_ENV_FILE 环境变量,它提供一个文件路径,您可以在其中为后续 Bash 命令持久化环境变量。

要设置单个环境变量,请将 export 语句写入 CLAUDE_ENV_FILE。使用追加 (>>) 来保留由其他 hooks 设置的变量:

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

要捕获设置命令中的所有环境更改,请比较之前和之后导出的变量:

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

Setup

仅当您使用 --init-only 启动 Claude Code,或在 非交互模式 中使用 --init 或 --maintenance 与 -p 标志时触发。它不会在正常启动时触发。用于一次性依赖安装或您从 CI 或脚本显式触发的计划清理,与正常会话启动分开。对于每个会话的初始化,请改用 SessionStart。

匹配器值对应于触发 hook 的 CLI 标志:

匹配器 何时触发
init claude --init-only 或 claude -p --init
maintenance claude -p --maintenance

当您运行 claude --init-only 时,Claude Code 运行 Setup hooks 和带有 startup 匹配器的 SessionStart hooks,然后退出而不启动对话。

当您使用 -p 启动或继续对话时,您还需要提供提示,作为参数或通过 stdin 管道传输。当 SessionStart hook 提供 initialUserMessage 或当您使用 延迟工具调用 恢复会话时,您可以跳过提示。

成功时,--init-only 不向终端打印任何内容。要确认 hooks 运行,请使用 claude --debug-file <path> --init-only 启动,将 <path> 替换为日志文件位置,并检查日志中的 Setup 和 SessionStart hook 条目。

由于 Setup 不会在每次启动时触发,需要安装依赖的插件不能仅依赖 Setup。实际的模式是在首次使用时检查依赖,如果缺失则安装,例如测试 ${CLAUDE_PLUGIN_DATA}/node_modules 的 hook 或 skill,如果不存在则运行 npm install。有关在何处存储已安装的依赖,请参阅 持久数据目录。如果您通过市场分发插件,您可能不需要此模式:Claude Code 在缓存插件时自动安装符合条件的 Node.js 包依赖。

Setup 输入

除了 常见输入字段 外,Setup hooks 接收设置为 "init" 或 "maintenance" 的 trigger 字段:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Setup",
  "trigger": "init"
}

Setup 决策控制

Setup hooks 无法阻止;执行在任何退出代码上继续。在每个退出代码上,Claude Code 丢弃 Setup hook 的 JSON 输出字段,如 systemMessage、continue 和 hookSpecificOutput.additionalContext。使用 -p 时,Setup hook 的 stdout、stderr 和退出代码仅在您使用 --output-format stream-json --verbose 启动时作为 hook_response 事件 出现在运行的输出中。

Setup hooks 可以访问 CLAUDE_ENV_FILE。写入该文件的变量持久化到会话的后续 Bash 命令中,就像在 SessionStart hooks 中一样。仅 type: "command" hooks 在 Setup 上运行。type: "mcp_tool" hook 在 Setup 上总是被跳过,如 MCP tool hook 字段 下所述。

InstructionsLoaded

在加载 CLAUDE.md 或 .claude/rules/*.md 文件到上下文时触发。此事件在会话启动时为急切加载的文件触发,稍后当文件被懒加载时再次触发,例如当 Claude 访问包含嵌套 CLAUDE.md 的子目录或当带有 paths: frontmatter 的条件规则匹配时。hook 不支持阻止或决策控制。它异步运行以用于可观测性目的。

当 Claude 直接通过 Project instructions 设置读取 AGENTS.md 时,此事件不触发。当 CLAUDE.md 导入您的 AGENTS.md 时,它会触发,load_reason 设置为 include(与任何其他导入文件一样),以及当 CLAUDE.md 是它的符号链接时,作为正常的 CLAUDE.md 加载。

匹配器针对 load_reason 运行。例如,使用 "matcher": "session_start" 仅为在会话启动时加载的文件触发,或 "matcher": "path_glob_match|nested_traversal" 仅为懒加载触发。

InstructionsLoaded 输入

除了 常见输入字段 外,InstructionsLoaded hooks 接收这些字段:

字段 描述
file_path 加载的指令文件的绝对路径
memory_type 文件的范围:"User"、"Project"、"Local" 或 "Managed"
load_reason 文件加载的原因:"session_start"、"nested_traversal"、"path_glob_match"、"include" 或 "compact"。"compact" 值在压缩事件后重新加载指令文件时触发
globs 文件的 paths: frontmatter 中的路径 glob 模式(如果有)。仅对 path_glob_match 加载出现
trigger_file_path 其访问触发此加载的文件的路径,用于懒加载
parent_file_path 包含此文件的父指令文件的路径,用于 include 加载
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "InstructionsLoaded",
  "file_path": "/Users/my-project/CLAUDE.md",
  "memory_type": "Project",
  "load_reason": "session_start"
}

InstructionsLoaded 决策控制

InstructionsLoaded hooks 没有决策控制。它们无法阻止或修改指令加载。Claude Code 丢弃它们的 JSON 输出字段,如 systemMessage 和 continue。使用此事件进行审计日志、合规性跟踪或可观测性。

UserPromptSubmit

在用户提交提示时运行,在 Claude 处理它之前。这允许您根据提示/对话添加额外上下文、验证提示或阻止某些类型的提示。

UserPromptSubmit hooks 对 command、http 和 mcp_tool 类型的默认超时为 30 秒,比大多数其他事件上这些类型的 600 秒默认值更短。因为此 hook 在每个提示之前运行并阻止模型处理直到它完成,卡住的 hook 会停滞会话。如果您的 hook 需要更多时间,请在 hook 条目中设置 timeout 字段。

除了您使用 async: true 运行的命令 hook 外,达到其超时的 UserPromptSubmit 命令、HTTP 或 MCP tool hook 被取消,其输出(包括任何 additionalContext)被丢弃。提示仍然到达 Claude 而没有该上下文。成绩单显示一个通知,命名 hook、触发的超时以及输出被丢弃。

在 UserPromptSubmit 上达到其超时的 Agent SDK 回调 hook 用命名 hook 和超时的消息阻止提示,因为那里的回调可以充当不能失败开放的策略门。会话继续。在 v2.1.208 之前,该事件上的回调超时以执行错误结束回合。

UserPromptSubmit 输入

除了 常见输入字段 外,UserPromptSubmit hooks 接收包含用户提交的文本的 prompt 字段。粘贴的内容折叠到 [Pasted text #N] 占位符会在原位展开到达。在 Claude Code 为 Claude 标记粘贴文本 的会话中,该展开的内容位于 <pasted_content id="…"> 行和 </pasted_content id="…"> 行之间,因此如果您的 hook 解析提示,请考虑这些行。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

UserPromptSubmit 决策控制

UserPromptSubmit hooks 可以控制是否处理用户提示并添加上下文。所有 JSON 输出字段 都可用。

有两种方式在退出代码 0 上向对话添加上下文:

  • 纯文本 stdout:Claude Code 将它 视为纯文本 的 stdout 添加到 Claude 的上下文
  • 带有 additionalContext 的 JSON:使用下面的 JSON 格式以获得更多控制。additionalContext 字段作为上下文添加

两个通道都不产生可见的成绩单条目。纯 stdout 和 additionalContext 值各自作为以 hook 名称开头的系统提醒注入;Claude 读取两者。要确认传递,请检查 调试日志。

要阻止提示,返回一个 decision 设置为 "block" 的 JSON 对象:

字段 描述
decision "block" 防止提示被处理并从上下文中删除它。省略以允许提示继续
reason 当 decision 为 "block" 时显示给用户。不添加到上下文
additionalContext 与提交的提示一起添加到 Claude 上下文的字符串。有关详细信息,请参阅 为 Claude 添加上下文
sessionTitle 设置会话标题。用于根据提示内容自动命名会话
suppressOriginalPrompt 如果在 decision 为 "block" 时为 true,则从显示给用户的阻止消息中省略原始提示文本

通过退出 2 阻止的 hook 路由方式与 reason 相同:阻止消息向用户显示 stderr 文本,它不添加到上下文。

{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title"
  }
}

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。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../00893aaf.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptExpansion",
  "expansion_type": "slash_command",
  "command_name": "example-skill",
  "command_args": "arg1 arg2",
  "command_source": "plugin",
  "prompt": "/example-skill arg1 arg2"
}

UserPromptExpansion 决策控制

UserPromptExpansion hooks 可以阻止扩展或添加上下文。所有 JSON 输出字段 都可用。

字段 描述
decision "block" 防止命令扩展。省略以允许它继续
reason 当 decision 为 "block" 时显示给用户
additionalContext 与扩展的提示一起添加到 Claude 上下文的字符串。有关详细信息,请参阅 为 Claude 添加上下文

通过退出 2 阻止的 hook 路由方式与 reason 相同:阻止消息向用户显示 stderr 文本。

{
  "decision": "block",
  "reason": "This slash command is not available",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptExpansion",
    "additionalContext": "Additional context for this expansion"
  }
}

MessageDisplay

在助手消息流向屏幕时运行。Claude Code 分批显示消息:每次一批新完成的行准备好渲染时,hook 运行一次,这些行,Claude Code 用 hook 的替换文本替换它们。长消息产生多个调用;短消息可能只产生一个。

使用 MessageDisplay 来:

  • 为最小显示剥离 markdown
  • 转换 Agent SDK 应用程序向其用户显示的文本
  • 从 Claude 的响应中编辑 API 密钥或内部主机名

Claude Code 保持每个批次直到您的 hook 返回,因此保持 hook 快速。如果 hook 失败或超时,Claude Code 显示原始文本。此事件的默认超时为 10 秒;如果您的 hook 需要更多时间,请在 hook 条目中设置 timeout 字段。

MessageDisplay 仅用于显示:替换文本仅更改屏幕上呈现的内容。成绩单和 Claude 看到的内容保持原始文本,因此 Claude 永远看不到替换,详细模式显示原始文本。hook 仅接收助手消息文本,因此工具结果和您输入的文本呈现不变。

MessageDisplay 不支持匹配器,对每个流式传输文本的助手消息触发;没有文本的消息(如仅工具调用响应)不触发它。

在非交互式运行中,包括 Agent SDK 查询和 claude -p,MessageDisplay 每个助手消息运行一次,而不是每批行运行一次。单个调用在消息完成后到达,并携带完整消息文本:index 为 0,final 为 true,delta 保持整个消息。为每个消息收集 delta 文本的 hook 在两种模式中接收相同的总文本。

MessageDisplay 输入

除了 常见输入字段 外,MessageDisplay hooks 接收回合和消息的标识符、此调用在消息中的位置以及 delta 中的新文本。批次边界取决于文本如何流式传输,因此使用 index 和 final 来跟踪通过消息的进度,而不是期望行以特定方式分组。

字段 描述
turn_id 当前回合的 UUID
message_id 正在显示的助手消息的 UUID。在同一消息的每个批次中稳定。这不是 API msg_… id,因此无法与成绩单消息 id 关联
index 此批次在消息中的零基索引
final 在消息的最后一个批次上为 true。每个消息恰好有一个最终批次
delta 自上一个批次以来新完成的行,包括终止换行符。始终是完整行,除了最终批次可能在行中间结束。在交互式运行中,当消息以换行符结束时,最终批次的 delta 为空,因此将 final 而不是非空 delta 视为消息结束信号。在 Agent SDK 和 claude -p 运行中,单个调用携带整个消息
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "MessageDisplay",
  "turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
  "message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
  "index": 0,
  "final": false,
  "delta": "Here is the plan:\n"
}

MessageDisplay 输出

除了所有 hooks 可用的 JSON 输出字段 外,MessageDisplay hooks 可以返回 displayContent 来替换屏幕上的 delta:

字段 描述
displayContent 显示代替 delta 的文本。省略以显示原始文本

MessageDisplay hooks 没有决策控制。它们无法阻止消息或更改成绩单中存储或发送给 Claude 的内容。Claude Code 从其 JSON 输出中作用于 displayContent 并丢弃 systemMessage 和 continue。

此示例从 Claude 的响应中剥离 markdown 格式以获得纯文本显示。脚本从 stdin 读取每个批次,从 delta 中删除粗体标记和内联代码反引号,并将结果作为 displayContent 返回。

在您的设置文件中为事件注册命令 hook:

{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}

将此脚本保存到项目中的 .claude/hooks/plain-display.sh 并使用 chmod +x 使其可执行:

#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'

没有 markdown 的批次通过不变。如果脚本失败,例如因为 jq 缺失,Claude Code 显示原始文本并仅在 调试输出 中注意失败,而不是在会话中。

PreToolUse

在 Claude 创建工具参数之后和处理工具调用之前运行。匹配除 EndConversation 外的任何工具名称:内置工具,如 Bash、PowerShell、Edit、Write、Read、Glob、Grep、Agent、Workflow、WebFetch、WebSearch、AskUserQuestion 和 ExitPlanMode,以及任何 MCP 工具名称。

要在特定文件在磁盘上更改时运行 hook,无论什么写入它,请改用 FileChanged 而不是按名称匹配文件编辑工具。与 PreToolUse 不同,Claude Code 在更改后运行 FileChanged hooks,它们没有决策控制,因此无法阻止写入。

使用 PreToolUse 决策控制 来允许、拒绝、询问或延迟工具调用。

在 PreToolUse 上超过其超时的 Agent SDK 回调 hook 阻止工具调用,Claude 接收命名超时的错误结果。另一个 hook 返回的显式拒绝仍然优先。

PreToolUse 输入

除了 常见输入字段 外,PreToolUse hooks 接收 tool_name、tool_input 和 tool_use_id。

对于 MCP 工具,输入还携带 mcp_server,一个包含服务器 name 和 source 的对象,说明服务器定义来自何处。source 值包括 plugin、sdk 和配置范围,如 user 和 project。Agent SDK 参考 中的 McpServerProvenance 列出了所有内容并说明如何处理您不认识的内容。基于 source 而不是 name 或 mcp__<server>__ 工具名称前缀做出信任决定。mcp_server 字段需要 Claude Code v2.1.274 或更高版本。

对于文件工具 Write、Edit 和 Read,tool_input.file_path 始终是绝对的:

  • Claude Code 在 hooks 运行之前扩展 ~ 和相对路径,因此匹配路径的 hook 无法通过 ~ 或相同路径的相对拼写绕过
  • 在 Windows 上,路径到达时带有反斜杠分隔符,即使您的 hook 在 Git Bash 下运行,其中 $PWD 看起来像 /c/project
  • 使用正斜杠编写的比较(如 /src/ 检查)永远不会匹配反斜杠路径,工具调用继续,就像 hook 没有什么要阻止的一样
  • 在比较之前规范化分隔符:Bash 中的 FILE_PATH="${FILE_PATH//\\//}",或 Python 中的 file_path.replace("\\", "/"),然后匹配路径段,如 /src/,而不是使用 ^ 锚定,因为路径是绝对的

Windows 上的 Write 调用传递:

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "C:\\project\\src\\index.ts",
    "content": "..."
  },
  ...
}

tool_input 字段取决于工具:

Bash

执行 shell 命令。

字段 类型 示例 描述
command string "npm test" 要执行的 shell 命令
description string "Run test suite" 命令执行操作的可选描述
timeout number 120000 可选超时(毫秒)。超过 最大值 的值被减少到最大值而不是被拒绝
run_in_background boolean false 是否在后台运行命令

当 Bash 命令更改 Git 存储库中的文件时,Claude Code 可以记录更改的内容。当 bashEditDiffEnabled 设置打开记录时,它在每个权限模式中记录更改;该设置的条目说明哪些文件可以设置它。否则它仅在自动模式和 bypassPermissions 模式中记录它们,并且仅当 Claude Code 指导 Claude 通过 Bash 编辑文件时。设置 bashEditDiffEnabled 为 false 以关闭记录。后台命令和只读命令不携带 diff。

您的 PostToolUse hook 然后在 tool_response.bashEditDiff 中接收更改的文件。列表涵盖命令运行时在存储库下更改的内容。Git 忽略的文件和子模块中的文件不被列出。需要 Claude Code v2.1.269 或更高版本。

changedFiles 和 files 列出命令更改的内容;其余字段说明该列表的完整性和可靠性。

字段 类型 示例 描述
changedFiles array ["/path/to/src/app.ts"] 命令更改的文件的绝对路径,最多 200 个。每当 files 保持 diff 或 moreFiles 高于零时出现
files array [{"filePath": "/path/to/src/app.ts", "hunks": [...]}] 最多 5 个更改文件的 diffs,用于显示。对于命令添加或删除的文件,created 或 deleted 为 true
moreFiles number 2 在 files 中没有 diff 的更改文件的计数
unavailable boolean true 当 diff 不完整或无法获取时设置
skipped boolean true 为移动工作树的 Git 命令设置,如 git checkout 或 git stash,因此 Claude Code 不获取 diff
shared boolean true 当另一个 Bash 工具调用(如子代理的)在同一存储库中同时运行时设置,因此某些列出的更改可能是该命令的
PowerShell

执行 PowerShell 命令。有关按平台的可用性,请参阅 PowerShell 工具。

字段与 Bash 工具匹配,命令字符串在 command 中:

字段 类型 示例 描述
command string "Get-ChildItem -Recurse" 要执行的 PowerShell 命令
description string "List files recursively" 命令执行操作的可选描述
timeout number 120000 可选超时(毫秒)
run_in_background boolean false 是否在后台运行命令

在检查 shell 命令的 hooks 中匹配 Bash|PowerShell,以便它们涵盖两个工具:

  • 在 Windows 上,无论 PowerShell 工具在何处启用,Claude 都将 PowerShell 视为主 shell 并通过它路由 shell 命令。
  • 在没有 Git Bash 的 Windows 上,工具自动启用,Claude Code 根本不注册 Bash 工具。
  • 仅匹配 Bash 的 hook 永远不会在那里触发。
Write

创建或覆盖文件。

字段 类型 示例 描述
file_path string "/path/to/file.txt" 要写入的文件的绝对路径
content string "file content" 要写入文件的内容
Edit

替换现有文件中的字符串。

字段 类型 示例 描述
file_path string "/path/to/file.txt" 要编辑的文件的绝对路径
old_string string "original text" 要查找和替换的文本
new_string string "replacement text" 替换文本
replace_all boolean false 是否替换所有出现
Read

读取文件内容。

字段 类型 示例 描述
file_path string "/path/to/file.txt" 要读取的文件的绝对路径
offset number 10 可选行号以开始读取
limit number 50 可选要读取的行数
Glob

查找与 glob 模式匹配的文件。

字段 类型 示例 描述
pattern string "**/*.ts" 要匹配文件的 glob 模式
path string "/path/to/dir" 可选要搜索的目录。默认为当前工作目录
Grep

使用正则表达式搜索文件内容。

字段 类型 示例 描述
pattern string "TODO.*fix" 要搜索的正则表达式模式
path string "/path/to/dir" 可选要搜索的文件或目录
glob string "*.ts" 可选 glob 模式以过滤文件
output_mode string "content" "content"、"files_with_matches" 或 "count"。默认为 "files_with_matches"
-i boolean true 不区分大小写的搜索
multiline boolean false 启用多行匹配
WebFetch

获取和处理网络内容。

字段 类型 示例 描述
url string "https://example.com/api" 要从中获取内容的 URL
prompt string "Extract the API endpoints" 在获取的内容上运行的提示
WebSearch

搜索网络。

字段 类型 示例 描述
query string "react hooks best practices" 搜索查询
allowed_domains array ["docs.example.com"] 可选:仅包含来自这些域的结果
blocked_domains array ["spam.example.com"] 可选:排除来自这些域的结果
Agent

生成 子代理。

字段 类型 示例 描述
prompt string "Find all API endpoints" agent 要执行的任务
description string "Find API endpoints" 任务的简短描述
subagent_type string "Explore" 要使用的专门 agent 类型
model string "sonnet" 可选模型别名以覆盖默认值

当前台 Agent 调用完成时,您的 PostToolUse hook 在 tool_response 中接收子代理的结果和运行遥测。读取这些字段以检查运行;对于跨子代理的令牌和成本汇总,使用 令牌和成本计数器 过滤到 query_source "subagent",因为 totalTokens 和 usage 仅涵盖最终请求:

字段 类型 示例 描述
status string "completed" 前台子代理为 "completed",后台子代理为 "async_launched"。从 v2.1.198 起,子代理默认在后台运行,因此省略的 run_in_background 也产生 "async_launched"
agentId string "a4d2c8f1e0b3a297" 子代理运行的标识符
content array [{"type": "text", "text": "Found 12 endpoints..."}] 子代理的最终文本块,或对于其报告通过 SubagentHandback 的子代理,关于该交接的简短说明代替
resolvedModel string "claude-sonnet-4-5" 子代理启动的模型,可能与请求的模型不同
modelsUsed array ["claude-sonnet-4-5", "claude-haiku-4-5"] 按顺序使用的模型,连续重复折叠;仅在模型在运行中交换时设置。需要 Claude Code v2.1.212 或更高版本
totalTokens number 12450 来自子代理最终 API 请求的令牌计数:输入、输出和缓存令牌合并。这不是整个运行的总计
totalDurationMs number 48211 子代理运行的挂钟持续时间
totalToolUseCount number 7 子代理进行的工具调用计数
usage object {"input_tokens": 8320, ...} 最终 API 请求的每类型令牌分解:input_tokens、output_tokens、cache_creation_input_tokens、cache_read_input_tokens

在 Claude Code v2.1.271 或更高版本上,使用 SubagentHandback 工具运行的子代理(Claude Code 在 自动模式 中提供)通过该工具而不是作为文本返回其报告。其 completed 结果的 content 字段然后携带关于该交接的简短说明,而不是报告本身。要读取报告,匹配 PreToolUse 或 PostToolUse hook 在 SubagentHandback 上并读取 tool_input.message。

对于后台子代理,工具在任务移到后台时返回,因此 tool_response 不携带使用字段:后台启动立即返回,前台任务在运行中被后台化时返回。它有 status: "async_launched"、agentId、description、prompt、outputFile 和 resolvedModel。

在 completed 响应上,resolvedModel 命名子代理启动的模型,可能与 tool_input 中的 model 值不同,例如当 availableModels 或另一个覆盖适用时。在 async_launched 响应上,resolvedModel 命名代理移到后台时使用的模型,因此在该之前发生的交换反映在那里。modelsUsed 和后台化时间 resolvedModel 行为需要 Claude Code v2.1.212 或更高版本。

AskUserQuestion

向用户提出一到四个多选题。

字段 类型 示例 描述
questions array [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}] 要呈现的问题,每个都有 question 字符串、简短 header、options 数组和可选 multiSelect 标志
answers object {"Which framework?": "React"} 可选。将问题文本映射到选定的选项标签。多选答案用逗号连接标签。Claude 不设置此字段;通过 updatedInput 提供它以以编程方式回答
ExitPlanMode

呈现计划并要求用户在 Claude 离开 plan mode 之前批准它。Claude 在调用工具之前将计划写入磁盘上的文件,因此来自模型的文字 tool_input 通常为空。Claude Code 在将输入传递给 hooks 之前注入计划内容和文件路径。

字段 类型 示例 描述
plan string "## Refactor auth\n1. Extract..." Markdown 中的计划内容。从磁盘上的计划文件注入
planFilePath string "/Users/.../plans/refactor-auth.md" 计划文件的路径。注入
allowedPrompts array [{"tool": "Bash", "prompt": "run tests"}] 已弃用。Claude Code 接受该字段但忽略它。在 v2.1.205 之前,它携带 Claude 请求实现计划的基于提示的权限

在 PostToolUse 中,tool_response 是一个包含 plan 和 filePath 字段的对象,保持批准的计划,加上内部状态标志。读取 tool_response.plan 以获取计划内容,而不是从磁盘重新读取文件。

PreToolUse 决策控制

PreToolUse hooks 可以控制工具调用是否继续。与使用顶级 decision 字段的其他 hooks 不同,PreToolUse 在 hookSpecificOutput 对象内返回其决策。这给了它更丰富的控制:四个结果(允许、拒绝、询问或延迟)加上在执行前修改工具输入的能力。

字段 描述
permissionDecision "allow" 跳过权限提示,除了 任何模式自动批准的操作 和对于 AskUserQuestion 和 ExitPlanMode,它们需要 updatedInput 与其配对。"deny" 防止工具调用。"ask" 提示用户确认。"defer" 优雅地退出,以便稍后可以恢复工具。拒绝和询问规则 仍然被评估,无论 hook 返回什么
permissionDecisionReason 对于 "allow" 和 "ask",显示给用户但不显示给 Claude。对于 "deny",显示给 Claude。对于 "defer",被忽略
updatedInput 在执行前修改工具的输入参数。替换整个输入对象,因此在修改的字段旁边包含未更改的字段。Claude Code 根据您的 hook 返回的输入而不是 Claude 发送的输入评估权限规则和 Bash 命令的 自动后台资格。与 "allow" 结合以自动批准,或与 "ask" 结合以向用户显示修改的输入。对于 "defer",被忽略
additionalContext 与工具结果一起添加到 Claude 上下文的字符串。当 permissionDecision 为 "defer" 时被忽略。有关详细信息,请参阅 为 Claude 添加上下文

当多个 PreToolUse hooks 返回不同的决策时,优先级为 deny > defer > ask > allow。

通过退出 2 阻止的 hook 路由方式与 "deny" 相同:Claude 看到 stderr 消息作为拒绝原因。

当 hook 返回 "ask" 时,显示给用户的权限提示包括一个标签,标识 hook 来自何处:[settings] 对于来自任何设置文件或 agent frontmatter 的 hook,[plugin:<name>] 对于插件的 hook,或 [skill] 对于来自 skill frontmatter 的 hook。这帮助用户理解哪个配置源请求确认。

hook 的 "ask" 也在 自动模式 中强制权限提示:分类器仍然可以拒绝工具调用,但它无法静默批准调用。在 v2.1.211 之前,分类器可以批准在 沙箱 外运行的 Bash 命令而不显示 hook 请求的提示;分类器仍然对该命令应用了自己的安全规则,hook "deny" 总是被尊重。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "My reason here",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}

在 非交互模式 中使用 -p 标志,Claude Code 仅在运行有 权限主机 来接收提示时提供 AskUserQuestion 和 ExitPlanMode,例如 Agent SDK canUseTool 回调。这些工具需要用户交互。返回 permissionDecision: "allow" 与 updatedInput 一起满足该要求:hook 从 stdin 读取工具的输入,通过您自己的 UI 收集答案,并在 updatedInput 中返回它,以便工具运行而不提示。仅返回 "allow" 对这些工具不充分。对于 AskUserQuestion,回显原始 questions 数组并添加一个 answers 对象,将每个问题的文本映射到选定的答案。

从 v2.1.199 起,一个 MCP 工具,其服务器用 _meta["anthropic/requiresUserInteraction"] 标记它,更严格:hook 无法用 "allow" 跳过其批准提示,无论是否有 updatedInput,因为 Claude Code 无法确认 hook 收集了工具需要的交互。

延迟工具调用以供稍后使用

"defer" 用于运行 claude -p 作为子进程并读取其 JSON 输出的集成,例如 Agent SDK 应用或构建在 Claude Code 之上的自定义 UI。它让该调用进程在工具调用处暂停 Claude,通过其自己的界面收集输入,并从中断处恢复。Claude Code 仅在 非交互模式 中使用 -p 标志时尊重此值。在交互式会话中,它记录警告并忽略 hook 结果。

AskUserQuestion 工具是典型情况:Claude 想问用户什么,但没有终端来回答。-p 运行仅在有 权限主机 时提供 AskUserQuestion,例如您使用 --permission-prompt-tool 传递的 MCP 工具,因此使用一个启动运行。往返工作如下:

  1. Claude 调用 AskUserQuestion。PreToolUse hook 触发。
  2. hook 返回 permissionDecision: "defer"。工具不执行。进程以 stop_reason: "tool_deferred" 退出,待处理的工具调用保留在成绩单中。
  3. 调用进程从 SDK 结果读取 deferred_tool_use,在其自己的 UI 中呈现问题,并等待答案。
  4. 调用进程运行 claude -p --resume <session-id>,带有相同的权限主机。相同的工具调用再次触发 PreToolUse。
  5. hook 返回 permissionDecision: "allow",答案在 updatedInput 中。工具执行,Claude 继续。

deferred_tool_use 字段携带工具的 id、name 和 input。input 是 Claude 为工具调用生成的参数,在执行前捕获:

{
  "type": "result",
  "subtype": "success",
  "stop_reason": "tool_deferred",
  "session_id": "abc123",
  "deferred_tool_use": {
    "id": "toolu_01abc",
    "name": "AskUserQuestion",
    "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
  }
}

没有超时或重试限制。会话保留在磁盘上,直到您恢复它,受 cleanupPeriodDays 保留扫描的约束,默认情况下在 30 天后删除会话文件,遵循 保留扫描规则。如果恢复时答案还没准备好,hook 可以再次返回 "defer",进程以相同的方式退出。调用进程通过最终从 hook 返回 "allow" 或 "deny" 来控制何时打破循环。

"defer" 仅在 Claude 在回合中进行单个工具调用时有效。如果 Claude 一次进行多个工具调用,"defer" 被忽略并显示警告,工具通过正常权限流程进行。约束存在是因为恢复只能重新运行一个工具:没有办法从批次中延迟一个调用而不留下其他未解决的。

如果恢复时延迟的工具不再可用,进程以 stop_reason: "tool_deferred_unavailable" 和 is_error: true 退出,hook 触发前。这发生在提供工具的 MCP 服务器对于恢复的会话未连接时。deferred_tool_use 有效负载仍然包含,以便您可以识别哪个工具丢失。

PermissionRequest

在 Claude Code 即将要求您获得工具使用权限时运行。在无法显示提示的会话中,例如 非交互模式 中的后台子代理,Claude Code 仍然运行这些 hooks,如果没有 hook 返回决策,它拒绝工具调用。 使用 PermissionRequest 决策控制 代表用户允许或拒绝。

当您需要 Claude 要求使用工具权限时的信号时使用此事件。Claude Code 仅在提示等待约六秒后运行带有 permission_prompt 类型的 Notification hook。

Claude Code 不为沙箱命令的 网络请求 运行 PermissionRequest hooks。要获得该提示的信号,请使用 permission_prompt 通知类型。

匹配工具名称,与 PreToolUse 相同的值。

PermissionRequest 输入

PermissionRequest hooks 接收 tool_name 和 tool_input 字段,如 PreToolUse hooks,但没有 tool_use_id。对于 MCP 工具,它们也接收 mcp_server 对象。可选的 permission_suggestions 数组包含 Claude Code 为此请求建议的 权限更新,例如添加允许规则或更改权限模式。

permission_suggestions 数组不是您看到的选项的确切列表,因为每个权限对话都构建自己的选项。某些对话(例如文件编辑的对话)根本不读取数组,并从请求本身派生其选项。读取它的对话仍然可以保留一个选项,其建议保留在数组中,例如当 allowManagedPermissionRulesOnly 隐藏规则保存选项时。它也可以提供数组中没有建议条目的选项,例如 Yes, and switch to auto mode,它直接更改权限模式而不是通过权限更新。

PreToolUse hooks 在每个工具调用之前运行,无论它是否需要权限。PermissionRequest hooks 仅在 Claude Code 即将要求您获得权限时运行,或当它否则会自动拒绝无法提示的调用时。两个事件都不为 EndConversation 触发。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}

PermissionRequest 决策控制

PermissionRequest hooks 可以允许或拒绝权限请求。除了所有 hooks 可用的 JSON 输出字段 外,您的 hook 脚本可以返回一个带有这些事件特定字段的 decision 对象:

字段 描述
behavior "allow" 授予权限,"deny" 拒绝它。拒绝和询问规则 仍然被评估,因此返回 "allow" 的 hook 不会覆盖匹配的拒绝规则
updatedInput 仅对 "allow":在执行前修改工具的输入参数。替换整个输入对象,因此在修改的字段旁边包含未更改的字段。修改的输入针对拒绝和询问规则重新评估
updatedPermissions 仅对 "allow":要应用的 权限更新条目 数组,例如添加允许规则或更改会话权限模式
message 仅对 "deny":告诉 Claude 为什么权限被拒绝
interrupt 仅对 "deny":如果为 true,停止 Claude

不带 decision 对象退出 2 的 hook 保持权限流程不变,其 stderr 被丢弃。仅 decision 对象可以授予或拒绝请求。

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

权限更新条目

updatedPermissions 输出字段和 permission_suggestions 输入字段 都使用相同的条目对象数组。每个条目有一个 type 来确定其他字段,以及一个 destination 来控制更改的写入位置。

type 字段 效果
addRules rules、behavior、destination 添加权限规则。rules 是 {toolName, ruleContent?} 对象的数组。省略 ruleContent 以匹配整个工具。behavior 是 "allow"、"deny" 或 "ask"
replaceRules rules、behavior、destination 用提供的 rules 替换 destination 处给定 behavior 的所有规则
removeRules rules、behavior、destination 删除给定 behavior 的匹配规则
setMode mode、destination 更改权限模式。有效模式为 default、auto、acceptEdits、dontAsk、bypassPermissions、plan 和 manual 作为 default 的别名。manual 别名需要 Claude Code v2.1.200 或更高版本
addDirectories directories、destination 添加工作目录。directories 是路径字符串的数组
removeDirectories directories、destination 删除工作目录

每个条目上的 destination 字段确定更改是保留在内存中还是持久化到设置文件。

destination 写入
session 仅在内存中,会话结束时丢弃
localSettings .claude/settings.local.json
projectSettings .claude/settings.json
userSettings ~/.claude/settings.json

hook 可以回显它接收的 permission_suggestions 之一作为其自己的 updatedPermissions 输出。

PostToolUse

在工具成功完成后立即运行。

匹配工具名称,与 PreToolUse 相同的值。

当工具名称不是正确的过滤器时更广泛地匹配:

  • 要在任何工具成功完成后运行 hook,省略 matcher 或将其设置为 "*"。您的 hook 然后可以自己发现更改了什么,例如通过运行 git status --porcelain,它也列出 git diff 错过的未跟踪文件。对于失败的工具调用,在 PostToolUseFailure 下添加相同的 hook。
  • 要在特定文件在磁盘上更改时运行 hook,无论什么写入它,请使用 FileChanged。当 Bash 命令或 Claude Code 外的进程重写相同文件时,Claude Code 不运行匹配 Edit|Write 的 PostToolUse hook。

PostToolUse 输入

PostToolUse hooks 在工具已经成功执行后触发。输入包括 tool_input(发送给工具的参数)和 tool_response(它返回的结果)。两者的确切模式取决于工具。文件工具 tool_input 路径以与 PreToolUse 相同的格式到达:始终绝对,带有平台的本机分隔符,因此 Windows 上的反斜杠。对于 MCP 工具,输入也携带 mcp_server 对象。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "type": "create"
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
字段 描述
duration_ms 可选。工具执行时间(毫秒)。不包括权限提示和 PreToolUse hooks 中花费的时间

PostToolUse 决策控制

PostToolUse hooks 可以在工具执行后提供反馈给 Claude。除了所有 hooks 可用的 JSON 输出字段 外,您的 hook 脚本可以返回这些事件特定的字段:

字段 描述
decision "block" 在工具结果旁边添加 reason。Claude 仍然看到原始输出;要替换它,请使用 updatedToolOutput
reason 当 decision 为 "block" 时显示给 Claude 的解释
additionalContext 与工具结果一起添加到 Claude 上下文的字符串。有关详细信息,请参阅 为 Claude 添加上下文
classifierContext 关于此调用结果的简短说明,用于 自动模式 分类器而不是 Claude。有关详细信息,请参阅 为自动模式分类器注释结果。需要 Claude Code v2.1.236 或更高版本
updatedToolOutput 在将工具的输出发送给 Claude 之前用提供的值替换它。该值必须与工具的输出形状匹配
updatedMCPToolOutput 仅替换 MCP 工具 的输出。优先使用 updatedToolOutput,它适用于所有工具

下面的示例替换 Bash 调用的输出。替换值与 Bash 工具的输出形状匹配:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude",
    "updatedToolOutput": {
      "stdout": "[redacted]",
      "stderr": "",
      "interrupted": false,
      "isImage": false
    }
  }
}

为自动模式分类器注释结果

返回 classifierContext 以向 自动模式 分类器而不是 Claude 发送关于工具调用结果的简短说明。分类器 永远不会接收工具结果本身,因此此字段是告诉它在审查后续操作之前关于调用返回的内容的支持方式。该字段需要 Claude Code v2.1.236 或更高版本。

下面的示例告诉分类器查询的输出来自何处:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "classifierContext": "This query ran against the staging database, not production."
  }
}

分类器给予说明的权重取决于您配置 hook 的位置:

  • 在 Claude Code 中配置的 Hooks:对于来自设置文件、插件、skills 和 agent frontmatter 的 hooks,分类器将说明视为未验证的、应用程序提供的上下文。说明永远不会建立用户意图,如果它声称您批准或请求了什么,分类器会根据您在对话中的自己的消息检查该声明
  • 进程内 Agent SDK 回调:当应用程序嵌入 Claude Code 并将 hook 注册为 TypeScript SDK 回调 并在实时会话期间返回说明时,分类器可能会将用户声明中继的说明视为用户意图。这样的声明可以满足分类器会从您发送的消息接受的同意要求,但它永远不会解除您自己的消息也无法解除的阻止。会话恢复后,Claude Code 将恢复的说明视为未验证的上下文。当两个组的 hooks 注释相同的调用时,分类器将组合说明视为未验证

Claude Code 在传递说明时应用这些限制:

  • 长度:Claude Code 将一个工具调用的说明上限为 2,000 个字符,并截断其余部分。上限在响应该调用的每个 hook 中共享
  • 仅同步响应:Claude Code 忽略 在后台运行 的 hook 响应中的字段,因为该响应在 Claude Code 记录工具结果后到达
  • 分类器不记录的调用:分类器的成绩单省略只读查找,例如文件读取和搜索。Claude Code 丢弃附加到其中一个调用的说明
  • 与重写的交互:当说明描述您用 updatedToolOutput 替换的输出时,在同一 hook 响应中返回两个字段。如果该重写被拒绝或另一个 hook 的重写替换它,Claude Code 丢弃说明。Claude Code 传递您返回的说明而不重写,即使另一个 hook 重写输出

PostToolUseFailure

在启动执行的工具失败时运行:工具抛出错误,或 MCP 工具返回错误结果。使用此来记录失败、发送警报或向 Claude 提供纠正反馈。

匹配工具名称,与 PreToolUse 相同的值。

PostToolUseFailure 输入

PostToolUseFailure hooks 接收与 PostToolUse 相同的 tool_name 和 tool_input 字段,以及错误信息作为顶级字段。对于 MCP 工具,它们也接收 mcp_server 对象。例如,失败的 npm test 命令可能传递:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUseFailure",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "tool_use_id": "toolu_01ABC123...",
  "error": "Exit code 1\nError: Cannot find module 'express'",
  "is_interrupt": false,
  "duration_ms": 4187
}
字段 描述
error 描述出错的字符串。格式取决于失败的工具
is_interrupt 可选布尔值。当失败作为中止而不是工具报告的错误到达 Claude Code 时为 True。取消运行的工具不触发此 hook;工具结果携带中断消息
duration_ms 可选。工具执行时间(毫秒)。不包括权限提示和 PreToolUse hooks 中花费的时间

error 字符串通常与 Claude 接收的失败工具结果相同的文本。其格式因工具和失败而异。在 tool_name、is_interrupt 和第一行 Exit code N 上键入您的 hook;将字符串的其余部分视为显示文本,而不是稳定格式。

  • 对于 Bash 和 PowerShell,运行并退出的命令产生第一行 Exit code N,然后是命令产生的任何输出作为一个块,stdout 和 stderr 交错
  • 有效负载也可能携带裸失败消息,没有退出代码行,当 Claude Code 无法启动 shell 进程本身时
  • Claude Code 中间截断长字符串,围绕 ... [N characters truncated] ... 标记,并可以插入自己的行,例如 Command timed out after 2m 0s

PostToolUseFailure 决策控制

PostToolUseFailure hooks 可以在工具失败后向 Claude 提供上下文。除了所有 hooks 可用的 JSON 输出字段 外,您的 hook 脚本可以返回这些事件特定的字段:

字段 描述
additionalContext 与错误一起添加到 Claude 上下文的字符串。有关详细信息,请参阅 为 Claude 添加上下文
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PostToolBatch

在批次中的每个工具调用都已解决后运行一次,在 Claude Code 向模型发送下一个请求之前。PostToolUse 每个工具运行一次,这意味着当 Claude 进行并行工具调用时它并发运行。PostToolBatch 恰好运行一次,带有完整批次,因此它是注入取决于运行的工具集而不是任何单个工具的上下文的正确位置。此事件没有匹配器。

PostToolBatch 输入

除了 常见输入字段 外,PostToolBatch hooks 接收 tool_calls,一个描述批次中每个工具调用的数组:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolBatch",
  "tool_calls": [
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/accounts.py"},
      "tool_use_id": "toolu_01...",
      "tool_response": "     1\tfrom __future__ import annotations\n     2\t..."
    },
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/transactions.py"},
      "tool_use_id": "toolu_02...",
      "tool_response": "     1\tfrom __future__ import annotations\n     2\t..."
    }
  ]
}

tool_response 包含模型在相应 tool_result 块中接收的相同内容。该值是序列化字符串或内容块数组,完全如工具发出的那样。对于 Read,这意味着行号前缀文本而不是原始文件内容。响应可能很大,因此仅解析您需要的字段。

PostToolBatch 决策控制

PostToolBatch hooks 可以为 Claude 注入上下文。除了所有 hooks 可用的 JSON 输出字段 外,您的 hook 脚本可以返回这些事件特定的字段:

字段 描述
additionalContext 在下一个模型调用之前注入一次的上下文字符串。有关传递详细信息、放入其中的内容以及恢复的会话如何处理过去的值,请参阅 为 Claude 添加上下文
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolBatch",
    "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
  }
}

返回 decision: "block" 或 continue: false 在下一个模型调用之前停止 agentic 循环。阻止消息来自 JSON reason 或 stopReason,或来自退出 2 的 stderr。您在成绩单中看到它作为警告,它保留在对话中,因此当对话继续时 Claude 看到它。

PermissionDenied

在 自动模式 拒绝工具调用时运行,包括当它拒绝而没有分类器判决时,因为 独立于自动模式的安全检查拒绝了分类器自己的请求 或其响应没有解析。此 hook 仅在自动模式中触发:当您手动拒绝权限对话、PreToolUse hook 阻止调用或 deny 规则匹配时,它不运行。使用它来记录拒绝、调整配置或告诉模型它可能重试工具调用。

匹配工具名称,与 PreToolUse 相同的值。

PermissionDenied 输入

除了 常见输入字段 外,PermissionDenied hooks 接收 tool_name、tool_input、tool_use_id 和 reason。对于 MCP 工具,它们也接收 mcp_server 对象。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "auto",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/build",
    "description": "Clean build directory"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "[Irreversible Local Destruction]"
}
字段 描述
reason 拒绝原因。对于分类器判决,在大多数会话中它命名方括号中的匹配规则,例如 [Data Exfiltration];有关其他形式,请参阅 审查拒绝。对于 无判决拒绝,它以 Auto mode could not evaluate this action and is blocking it for safety 开头。对于拒绝因为分类器模型不可用,它是固定文本 Classifier unavailable

PermissionDenied 决策控制

PermissionDenied hooks 可以告诉模型它可能重试被拒绝的工具调用。返回一个 hookSpecificOutput.retry 设置为 true 的 JSON 对象:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

当 retry 为 true 时,Claude Code 向对话添加一条消息,告诉模型它可能重试工具调用。Claude Code 不反转拒绝本身。如果您的 hook 不返回 JSON,或返回 retry: false,拒绝成立,模型接收原始拒绝消息。

当分类器对操作 产生无判决 时,Claude Code 忽略 retry: true:其响应没有解析,或独立于自动模式的安全检查拒绝了分类器自己的请求。对于这些拒绝,Claude Code 已经在拒绝消息中告诉模型是否稍后重试或继续。

Notification

在 Claude Code 发送通知时运行。匹配通知类型。省略匹配器以为所有通知类型运行 hooks。

即使关闭了桌面通知,您也会接收这些 hook 事件:preferredNotifChannel 设置(包括 notifications_disabled)仅更改您如何被警告,而不是您的 hook 是否运行。

匹配器 何时触发
permission_prompt Claude 需要您批准工具使用或沙箱命令的 网络请求,提示已等待约六秒
idle_prompt Claude 约 60 秒前完成响应,您自那以后没有输入
auth_success 身份验证完成
elicitation_dialog MCP 服务器打开引出表单,您约六秒没有输入
elicitation_url_dialog MCP 服务器要求您打开浏览器 URL,您约六秒没有输入
elicitation_complete MCP 服务器报告 URL 模式引出 完成
elicitation_response MCP 引出响应被发送回服务器
agent_needs_input 后台会话在 agent view 在终端中打开时开始等待您的输入,或当前会话询问您 agent team 队友的终端设置问题,您约六秒没有输入
agent_completed 后台会话完成或失败。仅在 agent view 在终端中打开时触发
quota_auto_resume_fired Claude Code 在 claude.ai 使用限制暂停它后继续您的任务:在重置时,或更早当您在 Claude Code 中做的事情(例如添加使用额度、升级您的计划或切换模型)使使用可用时,带有 模型设置异常
quota_auto_resume_stale claude.ai 使用限制在您的计算机睡眠超过约 30 分钟时重置。Claude Code 等待您按 Enter 而不是继续。在更短的睡眠后它继续并改为触发 quota_auto_resume_fired
quota_auto_resume_disabled Claude Code 结束其对 claude.ai 使用限制的等待而不继续您的任务:autoContinueAtUsageLimit 关闭或重置在 Claude Code 自己启动的等待期间移动超过 24 小时,继续的任务继续命中限制,或继续在到达模型之前被阻止。当您按 Esc 或 Ctrl+C 或选择 Don't continue automatically 时不触发

agent_needs_input 和 agent_completed 类型需要 Claude Code v2.1.198 或更高版本。

quota_auto_resume_fired、quota_auto_resume_stale 和 quota_auto_resume_disabled 类型需要 Claude Code v2.1.234 或更高版本。

在终端会话中,沙箱命令的网络请求的 permission_prompt 需要 Claude Code v2.1.246 或更高版本。

队友的终端设置问题的 agent_needs_input 需要 Claude Code v2.1.248 或更高版本。

Claude Code 在发送权限请求给 Agent SDK 的 canUseTool 回调 的会话中以不同方式计时 permission_prompt,这是 Claude Desktop 和 VS Code 扩展如何托管 Claude Code 的方式:

在 v2.1.233 之前,permission_prompt 在这些会话中不触发。

使用单独的匹配器根据通知类型运行不同的处理程序。此配置在 Claude 需要权限批准时触发权限特定的警报脚本,以及当 Claude 空闲时触发不同的通知:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/idle-notification.sh"
          }
        ]
      }
    ]
  }
}

Notification 输入

除了 常见输入字段 外,Notification hooks 接收 message 与通知文本、可选 title 和 notification_type 指示哪个类型触发。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

Notification hooks 无法阻止或修改通知。Claude Code 丢弃它们的 systemMessage 和 continue 字段,但仍然发出 terminalSequence,这是桌面通知示例所依赖的。Notification hooks 用于副作用,例如将通知转发到外部服务。

SubagentStart

在 Claude 使用 Agent 工具生成子代理时运行,当 Claude 恢复子代理 时,以及每次进程内 agent team 队友处理新消息时。支持匹配器以按 agent 类型名称过滤。对于内置 agents,这是 agent 名称,如 general-purpose、Explore 或 Plan。对于 自定义子代理,这是 agent 的 frontmatter 中的 name 字段,而不是文件名。

对于由 插件 提供的子代理,agent 类型是插件范围的标识符,例如 my-plugin:reviewer,而不是裸 frontmatter 名称。冒号将插件范围的名称放在正则表达式路径上,因此用 ^ 和 $ 锚定匹配器以获得精确匹配:^my-plugin:reviewer$。

SubagentStart 输入

除了 常见输入字段 外,SubagentStart hooks 接收 agent_id 与子代理的唯一标识符和 agent_type 与匹配器过滤的 agent 名称。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}

SubagentStart hooks 无法阻止子代理创建,但它们可以向子代理注入上下文。除了所有 hooks 可用的 JSON 输出字段 外,您可以返回:

字段 描述
additionalContext 在子代理对话开始时添加到子代理上下文的字符串,在其第一个提示之前。有关详细信息,请参阅 为 Claude 添加上下文
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Follow security guidelines for this task"
  }
}

当 hook 再次为同一子代理运行时,Claude Code 仅在子代理的上下文还不包含早期运行副本时注入返回的上下文。在启动时注入的副本保留在位置,保持子代理的 prompt cache 完整。在 自动压缩 丢弃该副本后,Claude Code 再次注入下一个运行的上下文。

SubagentStop

在 Claude Code 子代理完成响应时运行。匹配 agent 类型,与 SubagentStart 相同的值。

SubagentStop 输入

除了 常见输入字段 外,SubagentStop hooks 接收 stop_hook_active、agent_id、agent_type、agent_transcript_path 和 last_assistant_message。agent_type 字段是用于匹配器过滤的值。transcript_path 是主会话的成绩单,而 agent_transcript_path 是子代理自己的成绩单,存储在嵌套 subagents/ 文件夹中。last_assistant_message 字段包含子代理最终响应的文本内容,因此 hooks 可以访问它而无需解析成绩单文件。

不是每个 SubagentStop 事件都来自 Claude 生成的子代理。Claude Code 也为其某些自己的功能运行内部 agents,例如 prompt suggestions 和 /btw side questions,当其中一个完成时 SubagentStop 触发。对于这些事件,agent_type 是会话本身运行的 agent 名称,例如使用 --agent 或 agent 设置 设置的,以及当会话运行时没有一个时的空字符串。

不匹配空 agent_type 的命名 agent 类型的 matcher。一个其匹配器被省略、""、"*" 或是匹配空字符串的正则表达式的 hook 也为带有空 agent_type 的事件运行。

在 Claude Code v2.1.271 或更高版本上,使用 SubagentHandback 工具运行的子代理在停止之前通过该工具传递其报告。last_assistant_message 字段然后保持子代理的结束文本(如果有),这不是传递的报告。报告是该调用的 message 输入,PreToolUse 或 PostToolUse hook 匹配 SubagentHandback 接收作为 tool_input.message。

SubagentStop hooks 也接收 Stop input 下描述的 background_tasks 和 session_crons 数组。两个数组都限定于父会话,而不是子代理。

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../abc123.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
  "last_assistant_message": "Analysis complete. Found 3 potential issues...",
  "background_tasks": [],
  "session_crons": []
}

SubagentStop hooks 使用与 Stop hooks 相同的决策控制格式,包括 hookSpecificOutput.additionalContext,hookEventName 设置为 "SubagentStop",用于保持子代理运行的非错误反馈。返回 decision: "block" 与 reason 保持子代理运行并将 reason 作为其下一个指令传递给子代理。通过退出 2 阻止的 hook 以相同方式传递其 stderr 消息。要在子代理返回后向父会话注入上下文,请改用 PostToolUse hook 在 Agent 工具上。

TaskCreated

在通过 TaskCreate 工具创建任务时运行。使用此来强制命名约定、要求任务描述或防止某些任务被创建。在 没有 Task 工具的会话 中,此事件不触发。

TaskCreated hooks 不支持匹配器,在每个出现时触发。

TaskCreated 输入

除了 常见输入字段 外,TaskCreated hooks 接收 task_id、task_subject 和可选的 task_description、teammate_name 和 team_name。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "TaskCreated",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
字段 描述
task_id 正在创建的任务的标识符
task_subject 任务的标题
task_description 任务的详细描述。可能不存在
teammate_name 创建任务的队友的名称。可能不存在
team_name 已弃用。会话派生的团队名称;将在未来版本中删除

TaskCreated 决策控制

TaskCreated hook 可以通过两种方式阻止创建。无论哪种方式,Claude Code 删除任务并将您的消息作为工具的错误返回给 Claude。Claude Code 忽略此事件的 continue: false,Claude 继续工作。

  • 退出代码 2:Claude Code 将 stderr 文本作为消息返回。
  • JSON {"decision": "block", "reason": "..."}:Claude Code 将 reason 作为消息返回。

此示例阻止主题不遵循所需格式的任务:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
  echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
  exit 2
fi

exit 0

TaskCompleted

在任务被标记为完成时运行。这在两种情况下触发:当任何 agent 通过 TaskUpdate 工具显式标记任务为完成时,或当 agent team 队友完成其回合与进行中的任务时。使用此来强制完成标准,如通过测试或 lint 检查,然后任务才能关闭。

TaskCompleted hooks 不支持匹配器,在每个出现时触发。

TaskCompleted 输入

除了 常见输入字段 外,TaskCompleted hooks 接收 task_id、task_subject 和可选的 task_description、teammate_name 和 team_name。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
字段 描述
task_id 正在完成的任务的标识符
task_subject 任务的标题
task_description 任务的详细描述。可能不存在
teammate_name 完成任务的队友的名称。可能不存在
team_name 已弃用。会话派生的团队名称;将在未来版本中删除

TaskCompleted 决策控制

TaskCompleted hooks 支持两种方式来控制任务完成:

  • 退出代码 2:任务未被标记为完成,stderr 消息被反馈给模型作为反馈。
  • JSON {"continue": false, "stopReason": "..."}:当队友完成其回合触发事件时,完全停止队友,匹配 Stop hook 行为。stopReason 显示给用户。当 TaskUpdate 工具触发事件时,Claude Code 忽略 continue: false;退出代码 2 仍然阻止完成。

此示例运行测试并在它们失败时阻止任务完成:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# Run the test suite
if ! npm test 2>&1; then
  echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
  exit 2
fi

exit 0

Stop

在主 Claude Code agent 完成响应时运行。如果停止由于用户中断而发生,则不运行。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 在 8 个连续阻止后覆盖 hook 并结束回合。

last_assistant_message 字段包含 Claude 最终响应的文本内容,因此 hooks 可以访问它而无需解析成绩单文件。对于作用于刚完成的回合的 hooks,例如朗读或通知 hooks,使用此字段而不是读取 transcript_path:成绩单文件不保证在所有版本的 Stop 时包含最终消息。

background_tasks 和 session_crons 数组让 hooks 区分"会话完成"与"会话暂停等待后台工作唤醒它"。当任务注册表可达时两个数组都出现,当没有任何东西在飞行或计划时为空。

background_tasks 中的每个条目描述一个进行中的任务,并使用这些字段:

字段 描述
id 任务标识符
type 友好的任务类型标签,例如 shell、subagent、monitor、workflow、teammate、cloud session 或 MCP task。每个标签标识哪个 Claude Code 功能创建了任务。对于无法识别的类型回退到原始判别式
status 当前任务状态
description 自由文本描述,上限为 1000 个字符,当剪裁时带有字符串内 … [+N chars] 标记
command Shell 命令行,上限为 1000 个字符。仅对 shell 任务出现
agent_type 子代理类型名称。仅对 subagent 任务出现
server MCP 服务器名称。仅对 monitor 和 MCP task 任务出现
tool MCP 工具名称。仅对 monitor 和 MCP task 任务出现
name 工作流名称。仅对 workflow 任务出现

session_crons 中的每个条目描述一个会话范围的计划唤醒,来自 CronCreate、ScheduleWakeup 和 /loop:

字段 描述
id Cron 任务标识符
schedule Cron 表达式,例如 0 9 * * 1-5
recurring 对于一次性唤醒(其计划编码单个触发时间)为 false,对于在每个匹配上重新触发的任务为 true
prompt 当 cron 触发时提交的提示,上限为 1000 个字符,带有相同的 … [+N chars] 标记

此示例显示了一个进行中的 shell 任务和一个循环 cron 的 Stop 输入:

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": true,
  "last_assistant_message": "I've completed the refactoring. Here's a summary...",
  "background_tasks": [
    {
      "id": "task-001",
      "type": "shell",
      "status": "running",
      "description": "tail logs",
      "command": "tail -f /var/log/syslog"
    }
  ],
  "session_crons": [
    {
      "id": "cron-001",
      "schedule": "0 9 * * 1-5",
      "recurring": true,
      "prompt": "check the build"
    }
  ]
}

Stop 决策控制

Stop 和 SubagentStop hooks 可以控制 Claude 是否继续。除了所有 hooks 可用的 JSON 输出字段 外,您的 hook 脚本可以返回这些事件特定的字段:

字段 描述
decision "block" 防止 Claude 停止。省略以允许 Claude 停止
reason 当 decision 为 "block" 时需要。告诉 Claude 为什么它应该继续
hookSpecificOutput.additionalContext 对 Claude 的非错误反馈。对话继续,以便 Claude 可以对其采取行动,但与 decision: "block" 不同,它在成绩单中显示为 hook 反馈而不是 hook 错误

通过退出 2 阻止的 hook 路由方式与 reason 相同:Claude 接收 stderr 消息作为为什么它应该继续的解释。

{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}

当 hook 按设计工作并给 Claude 指导时使用 additionalContext,例如"在完成前运行测试套件"。它通过与 decision: "block" 相同的循环保护保持对话进行,即 stop_hook_active 输入和 8 个连续继续上限,但成绩单将其标记为 Stop hook feedback,不显示 hook 错误通知:

{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Please run the test suite before finishing"
  }
}

StopFailure

在回合由于 API 错误而结束时运行,而不是 Stop。Claude Code 忽略 hook 的输出和退出代码,除了 terminalSequence。使用此来记录失败、发送警报或在 Claude 由于速率限制、身份验证问题或其他 API 错误而无法完成响应时采取恢复操作。

StopFailure 输入

除了 常见输入字段 外,StopFailure hooks 接收 error、可选的 error_details 和可选的 last_assistant_message。error 字段标识错误类型,用于匹配器过滤。

字段 描述
error 错误类型:rate_limit、overloaded、authentication_failed、oauth_org_not_allowed、account_on_hold、billing_error、invalid_request、model_not_found、server_error、max_output_tokens、cloud_credential_error 或 unknown
error_details 关于错误的其他详细信息(如果可用)
last_assistant_message 在对话中显示的呈现错误文本。与 Stop 和 SubagentStop 不同,其中此字段保持 Claude 的对话输出,对于 StopFailure 它包含 API 错误字符串本身,例如 "API Error: Rate limit reached"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "StopFailure",
  "error": "rate_limit",
  "error_details": "429 Too Many Requests",
  "last_assistant_message": "API Error: Rate limit reached"
}

StopFailure hooks 没有决策控制。它们仅为通知和日志记录目的运行。

TeammateIdle

在 agent team 队友完成其回合后即将空闲时运行。使用此来强制质量门,如要求通过 lint 检查或验证输出文件存在。

TeammateIdle hooks 不支持匹配器,在每个出现时触发。

TeammateIdle 输入

除了 常见输入字段 外,TeammateIdle hooks 接收 teammate_name 和 team_name。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "session-a1b2c3d4"
}
字段 描述
teammate_name 即将空闲的队友的名称
team_name 已弃用。会话派生的团队名称;将在未来版本中删除

TeammateIdle 决策控制

TeammateIdle hooks 支持两种方式来控制队友行为:

  • 退出代码 2:队友接收 stderr 消息作为反馈并继续工作而不是空闲。
  • JSON {"continue": false, "stopReason": "..."}:完全停止队友,匹配 Stop hook 行为。stopReason 显示给用户。

此示例检查构建工件是否存在,然后允许队友空闲:

#!/bin/bash

if [ ! -f "./dist/output.js" ]; then
  echo "Build artifact missing. Run the build before stopping." >&2
  exit 2
fi

exit 0

ConfigChange

在会话期间配置文件更改时运行。使用此来审计设置更改、强制安全策略或阻止对配置文件的未授权修改。

Claude Code 在设置文件、托管策略文件或 skill 文件更改时运行 ConfigChange hooks。对于托管策略,它仅在 managed-settings.json 或 managed-settings.d/ 中的文件更改时运行它们。它应用 服务器托管设置 和对 macOS 托管首选项或 Windows 注册表策略的更改而不运行它们。在 WSL 上使用 wslInheritsWindowsSettings,它也在其策略轮询上应用更改的 Windows 端托管设置文件而不运行它们。

匹配器过滤配置源:

匹配器 何时触发
user_settings ~/.claude/settings.json 更改
project_settings .claude/settings.json 更改
local_settings .claude/settings.local.json 更改
policy_settings managed-settings.json 或 managed-settings.d/ 中的文件更改
skills .claude/skills/ 中的 skill 文件更改

此示例记录所有配置更改以进行安全审计:

{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

ConfigChange 输入

除了 常见输入字段 外,ConfigChange hooks 接收 source 和可选的 file_path。source 字段指示哪个配置类型更改,file_path 提供修改的特定文件的路径。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ConfigChange",
  "source": "project_settings",
  "file_path": "/Users/.../my-project/.claude/settings.json"
}

ConfigChange 决策控制

ConfigChange hooks 可以阻止配置更改生效。使用退出代码 2 或 JSON decision 来防止更改。当被阻止时,新设置不应用于运行的会话。

字段 描述
decision "block" 防止配置更改被应用。省略以允许更改
reason 接受但永远不显示
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

policy_settings 更改无法被阻止。当机器上的托管设置文件更改时,Hooks 仍然为 policy_settings 源触发,因此您可以使用它们来记录这些编辑,但任何阻止决策都被忽略。这确保企业托管设置始终生效。当 服务器托管设置 到达或刷新时,Claude Code 不运行 ConfigChange hooks。

Claude Code 从 ConfigChange hook 的 JSON 输出中作用于阻止决策,并丢弃 systemMessage 和 continue。被阻止的更改不向您或 Claude 显示任何消息,无论您是用 reason 还是退出 2 的 stderr 阻止。Claude Code 仅向调试日志写入一行。

CwdChanged

在主对话中的 shell 命令更改工作目录时运行,例如当 Claude 执行 cd 命令时。使用此来对目录更改做出反应:重新加载环境变量、激活项目特定的工具链或自动运行设置脚本。与 FileChanged 配对,用于 direnv 等管理每个目录环境的工具。

CwdChanged hooks 可以访问 CLAUDE_ENV_FILE。写入该文件的变量持久化到后续 Bash 命令,直到下一个 CwdChanged 事件,当 Claude Code 清除它们时。

CwdChanged 不支持匹配器,在每个出现时触发。

CwdChanged 输入

除了 常见输入字段 外,CwdChanged hooks 接收 old_cwd 和 new_cwd。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project/src",
  "hook_event_name": "CwdChanged",
  "old_cwd": "/Users/my-project",
  "new_cwd": "/Users/my-project/src"
}

CwdChanged 输出

除了所有 hooks 可用的 JSON 输出字段 外,CwdChanged hooks 可以返回 watchPaths 来动态设置哪些文件路径 FileChanged 监视:

字段 描述
watchPaths 绝对路径的数组。替换当前动态监视列表。来自您 matcher 配置的路径始终被监视。返回空数组清除动态列表,这在进入新目录时是典型的

CwdChanged hooks 没有决策控制。它们无法阻止目录更改。

Claude Code 从其 JSON 输出读取 watchPaths 和 systemMessage,并丢弃 continue。在交互式会话中,它显示 systemMessage 作为简短的终端通知。消息不到达 SDK 消息流。

DirectoryAdded

在您使用 /add-dir 命令或 SDK 客户端使用 register_repo_root 控制请求在会话中添加工作目录后运行。使用此来准备新添加的存储库,例如安装其依赖。

Claude Code 在以下情况下不触发此事件:

  • 您使用 --add-dir 启动标志传递目录;SessionStart 涵盖这些目录
  • 您在 /permissions Workspace 选项卡上添加目录
  • 您添加已经是工作目录或在其中的目录

Claude Code 在刷新沙箱和权限状态后触发 DirectoryAdded,因此沙箱工具已经在您的 hook 运行时看到新目录。Hook 命令本身运行未沙箱化。

Claude Code 不等待 hook:添加立即完成,hook 在后台以 600 秒默认超时运行。

匹配器过滤目录的添加方式:

匹配器 何时触发
slash_command 您使用 /add-dir 添加目录
register_repo_root SDK 客户端使用 register_repo_root 控制请求添加目录

DirectoryAdded 输入

除了 常见输入字段 外,DirectoryAdded hooks 接收 directory 和 source。

字段 描述
directory 添加的目录的绝对路径
source 目录如何被添加,/add-dir 为 "slash_command" 或 SDK 控制请求为 "register_repo_root"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "DirectoryAdded",
  "directory": "/Users/my-other-repo",
  "source": "slash_command"
}

DirectoryAdded hooks 没有决策控制。它们无法阻止添加,这在 hook 运行时已经完成。Claude Code 根据源以不同方式处理其 JSON 输出中的 systemMessage 和失败输出:

  • slash_command:Claude Code 将 hook 的 systemMessage 传递给 Claude 作为下一个对话回合的上下文,而不是向您显示它。失败 hooks 的计数出现在成绩单中。完整失败输出进入调试日志
  • register_repo_root:Claude Code 仅将 systemMessage 输出和失败输出写入调试日志

FileChanged

在监视的文件在磁盘上更改时运行。Claude Code 使用文件系统监视器检测更改,而不是通过检查工具调用,因此无论什么更改文件,它都运行 hook:Write 或 Edit 工具调用、Claude 使用 Bash 运行的脚本或 Claude Code 外的进程。常见用途是在项目配置文件更改时重新加载环境变量。

此事件的 matcher 有两个角色:

  • 构建监视列表:值在 | 上分割,每个段注册为工作目录中的文字文件名,因此 ".envrc|.env" 恰好监视这两个文件。正则表达式模式在这里不有用:像 ^\.env 这样的值会监视一个字面上命名为 ^\.env 的文件。
  • 过滤哪些 hooks 运行:当监视的文件更改时,相同的值使用标准 匹配器规则 针对更改文件的基名过滤哪些 hook 组运行。

此示例在任何更改后规范化 data.csv 中的行结尾,包括 Bash 命令或外部脚本重写文件:

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": "data.csv",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/normalize-line-endings.sh"
          }
        ]
      }
    ]
  }
}

hook 从 stdin 上的 JSON 输入 的 file_path 字段读取更改文件的绝对路径。其 grep 守卫测试与 perl 删除的相同内容,行末的 CR,因此规范化后的运行退出而不触及文件。更松散的守卫循环永远,因为 perl -i 重写文件,即使它替换了什么,Claude Code 在每次重写后运行 hook。将此脚本保存在 /path/to/normalize-line-endings.sh 并使其可执行:

#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
  perl -pi -e 's/\r$//' "$FILE"
fi

要确认 hook 有效,要求 Claude 使用 Bash 命令将 CRLF 行附加到 data.csv。Claude Code 运行 hook,文件最终以 LF 结尾。

要监视您无法提前命名的文件,从 hook 返回 watchPaths 来动态更新监视列表。Claude Code 仅在某些东西命名要监视的文件时启动监视器,因此使用至少命名一个文件的 FileChanged 组为列表播种,或使用 SessionStart 或 CwdChanged hook 返回 watchPaths。匹配器仍然过滤当监视的文件更改时哪些 hook 组运行,因此给处理动态路径的组一个省略的匹配器,它匹配每个监视的文件并不向监视列表添加任何内容。"*" 匹配器也匹配每个文件,但 Claude Code 像任何其他值一样在监视列表中注册它,作为一个字面上命名为 * 的文件。

FileChanged hooks 可以访问 CLAUDE_ENV_FILE。写入该文件的变量持久化到后续 Bash 命令,直到下一个 CwdChanged 事件,当 Claude Code 清除它们时。

FileChanged 输入

除了 常见输入字段 外,FileChanged hooks 接收 file_path 和 event。

字段 描述
file_path 更改的文件的绝对路径
event 发生了什么:修改文件为 "change"、创建的文件为 "add" 或删除的文件为 "unlink"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "FileChanged",
  "file_path": "/Users/my-project/.envrc",
  "event": "change"
}

FileChanged 输出

除了所有 hooks 可用的 JSON 输出字段 外,FileChanged hooks 可以返回 watchPaths 来动态更新哪些文件路径被监视:

字段 描述
watchPaths 绝对路径的数组。替换当前动态监视列表。来自您 matcher 配置的路径始终被监视。当您的 hook 脚本根据更改的文件发现要监视的其他文件时使用此

FileChanged hooks 没有决策控制。它们无法阻止文件更改发生。

Claude Code 从其 JSON 输出读取 watchPaths 和 systemMessage,并丢弃 continue。在交互式会话中,它显示 systemMessage 作为简短的终端通知。消息不到达 SDK 消息流。

WorktreeCreate

在创建 worktree 时运行,无论是从 claude --worktree、从 使用 isolation: "worktree" 的子代理,还是为 Claude Code 在其自己的 worktree 中隔离的 后台会话。默认情况下,Claude Code 使用 git worktree 创建隔离的工作副本。配置 WorktreeCreate hook 替换该默认 git 行为,让您使用不同的版本控制系统,如 SVN、Perforce 或 Mercurial。

因为 hook 完全替换默认行为,.worktreeinclude 不被处理。如果您需要将本地配置文件(如 .env)复制到新 worktree,请在您的 hook 脚本中执行。

hook 必须返回创建的 worktree 目录的路径。Claude Code 使用此路径作为隔离会话的工作目录。有关每个 hook 类型如何返回路径,请参阅 WorktreeCreate 输出。

Claude Code 作用于 hook 的成功和返回的路径,并丢弃 systemMessage 和 continue。

此示例创建 SVN 工作副本并打印路径供 Claude Code 使用。将存储库 URL 替换为您自己的:

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
          }
        ]
      }
    ]
  }
}

hook 从 stdin 上的 JSON 输入读取 worktree name,检出一个新副本到新目录,并打印目录路径。最后一行的 echo 是 Claude Code 读取为 worktree 路径的内容。将任何其他输出重定向到 stderr,以便它不会干扰路径。

WorktreeCreate 输入

除了 常见输入字段 外,WorktreeCreate hooks 接收 name 字段。这是新 worktree 的 slug 标识符,由用户指定或自动生成,例如 bold-oak-a3f2。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeCreate",
  "name": "feature-auth"
}

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" } }。

如果 hook 失败或产生无路径,worktree 创建失败并出现错误。

Claude Code 根据 hook 运行的目录解析相对路径,折叠其中的任何 . 或 .. 段。如果结果路径不是 Claude Code 可以进入的目录,会话打印命名路径的错误并以代码 1 退出。

Claude Code 拒绝包含 . 或 .. 段的绝对路径,以及通过存储库根下的符号链接的任何路径,因为提交到存储库的符号链接可能会将 worktree 重定向到其外。错误命名被拒绝的组件。返回不通过存储库内符号链接的规范化路径。在 v2.1.216 之前,worktree 创建遵循 hook 的路径而不进行此筛选。

WorktreeRemove

在删除 worktree 时运行。这是 WorktreeCreate 的清理对应物。事件在以下情况下触发:

  • 您退出 --worktree 会话并选择删除它
  • 带有 isolation: "worktree" 的子代理完成
  • 您删除 后台会话,其 worktree hook 创建

对于基于 git 的 worktrees,Claude Code 使用 git worktree remove 自动处理清理。如果您为非 git 版本控制系统配置了 WorktreeCreate hook,请将其与 WorktreeRemove hook 配对以处理清理。没有它,worktree 目录留在磁盘上。

对于后台会话删除,Claude Code 在运行 hook 之前验证存储的 worktree 路径,并拒绝是符号链接或通过存储库根下的符号链接的路径。hook 仅对仍包含文件的 worktree 运行,当您在 agent view 中确认删除时;对于这样的 worktree,claude rm 保持会话和 worktree。在 v2.1.216 之前,hook 在存储的路径上运行而不进行这些检查。

Claude Code 将 WorktreeCreate 返回的路径作为 worktree_path 在 hook 输入中传递。此示例读取该路径并删除目录:

{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
          }
        ]
      }
    ]
  }
}

WorktreeRemove 输入

除了 常见输入字段 外,WorktreeRemove hooks 接收 worktree_path 字段,这是被删除的 worktree 的绝对路径。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeRemove",
  "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}

WorktreeRemove hook 的退出代码决定结果。当 hook 退出非零且 worktree_path 处的目录仍然存在时,删除失败:

  • worktree 保留在磁盘上,hook 的命令和 stderr 进入 调试日志。
  • 如果您删除后台会话,会话也保留。agent view 中的拒绝消息报告 hook 如何结束,例如 exited 1,引用其 stderr 的开头,并说是否再次删除会话无论如何删除目录。

PreCompact

在 Claude Code 即将运行压缩操作之前运行。

匹配器值指示压缩是手动还是自动触发:

匹配器 何时触发
manual /compact
auto 当对话达到 自动压缩窗口 时自动压缩

使用代码 2 退出以阻止压缩。对于手动 /compact,stderr 消息显示给用户。您也可以通过返回带有 "decision": "block" 的 JSON 来阻止。

阻止自动压缩根据何时触发有不同的效果。如果压缩在上下文限制之前主动触发,Claude Code 跳过它,对话继续未压缩。如果压缩被触发以从 API 已返回的上下文限制错误恢复,基础错误浮出并且当前请求失败。

Claude Code 丢弃 PreCompact hook 的 systemMessage 和 continue 字段。

PreCompact 输入

除了 常见输入字段 外,PreCompact hooks 接收 trigger 和 custom_instructions。对于 manual,custom_instructions 包含用户传递到 /compact 的内容,当他们传递什么都不传递时为 null。对于 auto,custom_instructions 为 null。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": null
}

PostCompact

在 Claude Code 完成压缩操作后运行。使用此事件对新压缩状态做出反应,例如记录生成的摘要或更新外部状态。Claude Code 丢弃 PostCompact hook 的 systemMessage 和 continue 字段。

与 PreCompact 相同的匹配器值适用:

匹配器 何时触发
manual 在 /compact 后
auto 当对话达到 自动压缩窗口 时自动压缩后

PostCompact 输入

除了 常见输入字段 外,PostCompact hooks 接收 trigger 和 compact_summary。compact_summary 字段包含压缩操作生成的对话摘要。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostCompact",
  "trigger": "manual",
  "compact_summary": "Summary of the compacted conversation..."
}

PostCompact hooks 没有决策控制。它们无法影响压缩结果,但可以执行后续任务。

PreModelSwitch

在 Claude Code 应用您或客户端请求的模型切换之前运行。使用它来阻止切换、要求确认或在切换发生之前显示成本。

PreModelSwitch 需要 Claude Code v2.1.251 或更高版本。Claude Code 为这些请求运行它:

  • /model <name> 和 /model 选择器
  • Option+P 或 Alt+P 模型选择器
  • /config 中的 Model 设置
  • 当那改变会话的模型时打开 fast mode
  • 来自 Agent SDK 主机或 Remote Control 的 set_model 请求,或 apply_flag_settings 请求中的模型更改

Claude Code 不为它自己进行的切换运行 PreModelSwitch hooks,例如 自动模型回退 或恢复会话时恢复模型。这些更改仅到达 PostModelSwitch。

Claude Code 将匹配器与会话切换到的模型的规范名称进行比较,忽略任何 [1m] 后缀。别名(如 opus)、日期模型 ID 和提供商特定 ID(如 Amazon Bedrock 模型 ID)都匹配它们解析到的一个规范名称,因此 claude-opus-5 涵盖 Opus 5 的每个拼写。

当 Claude Code 无法确定目标的规范名称时,例如仅您的 LLM gateway 知道的自定义模型 ID,它运行每个 PreModelSwitch hook,无论匹配器如何。阻止的 hook 应该从其输入检查 to_model 而不是仅依赖匹配器。

将匹配器写为精确名称、| 分隔列表(如 claude-opus-4-6|claude-opus-5)或正则表达式(如 .*opus.*)。此示例使用精确名称匹配器,也从 hook 输入检查 to_model,因此它拒绝切换到 Opus 4.6,通过退出代码 2,并让任何其他目标通过:

命令使用 jq 检查 to_model:

{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"
}
]
}
]
}
}

要确认 hook 有效,从运行不同模型的会话运行 /model claude-opus-4-6。Claude Code 保持当前模型并报告 PreModelSwitch hook 阻止了切换,以您的消息作为原因。

PreModelSwitch 输入

除了 常见输入字段 外,PreModelSwitch hooks 接收此表中的字段。最后五个描述重新发送对话到新模型的成本,因此 hook 可以在切换发生之前显示该数字。

字段 类型 描述
from_model string 切换更改的模型 ID
to_model string 切换更改为的模型 ID。匹配器与此模型的规范名称进行比较
requested_model string or null 请求命名的模型:别名(如 opus)、完整模型 ID 或当请求为默认模型时 null
source string 请求来自何处:"command" 用于 /model <name>、/config 中的 Model 设置或打开 fast mode;"picker" 用于模型选择器;"sdk" 用于 set_model 请求,或来自 Agent SDK 主机或 Remote Control 的 apply_flag_settings 请求中的模型更改
context_tokens number 下一个请求重新发送作为其提示的令牌:主对话中最后响应的输入、缓存读取、缓存创建和输出令牌,合并。第一个响应前为 0
prompt_cache_warm boolean 当前模型的 prompt cache 是否可能仍然温暖,意味着切换放弃它
cache_ttl string Prompt cache 生命周期 Claude Code 为此会话请求:"5m" 或 "1h"
estimated_cache_write_usd number 将 context_tokens 写入 to_model 上的 prompt cache 的估计成本(美元),以 cache_ttl 速率,不包括下一个响应
pricing string Claude Code 如何定价 estimated_cache_write_usd:当您的组织配置了它们时在您的组织自己的速率处为 "configured",在列表价格处为 "catalog",或当 to_model 没有已知价格且 Claude Code 假设默认速率时为 "default"

此示例显示了在运行 Sonnet 5 的会话中 /model opus 的输入:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreModelSwitch",
  "from_model": "claude-sonnet-5",
  "to_model": "claude-opus-5",
  "requested_model": "opus",
  "source": "command",
  "context_tokens": 182340,
  "prompt_cache_warm": true,
  "cache_ttl": "5m",
  "estimated_cache_write_usd": 1.1396,
  "pricing": "catalog"
}

PreModelSwitch 决策控制

PreModelSwitch hooks 可以取消切换、要求用户确认或让它继续。退出代码 2 或顶级 decision: "block" 取消切换。

为了更精细的控制,在 hookSpecificOutput 对象中返回 permissionDecision 和 permissionDecisionReason,如 PreToolUse 上。PreModelSwitch 接受 "allow"、"deny" 和 "ask"。它不接受 "defer"、updatedInput 或 additionalContext。下表描述两个字段:

字段 描述
permissionDecision "allow" 继续并跳过 Claude Code 在 prompt cache 温暖时显示的确认。"deny" 取消切换。"ask" 提示用户确认它
permissionDecisionReason 对于 "deny",显示给用户作为切换被阻止的原因,或为 set_model 请求返回为错误。对于 "ask",显示在确认提示中。对于 "allow" 被忽略

仅交互式会话中的 /model 可以显示 "ask" 提示。在每个其他表面,包括带 -p 标志的非交互模式、/config 和 set_model 请求,Claude Code 将 "ask" 视为拒绝。

此示例要求用户确认并引用来自 context_tokens 的令牌计数:

{
  "hookSpecificOutput": {
    "hookEventName": "PreModelSwitch",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
  }
}

当多个 PreModelSwitch hooks 返回不同的决策时,优先级为 deny > ask > allow。

Claude Code 显示您的 hook 返回的任何 systemMessage 给用户,无论决策如何,因此成本报告 hook 可以返回 {"systemMessage": "..."} 并退出 0。

在其超时前不响应的 PreModelSwitch hook 阻止切换。在 PreToolUse 上,相比之下,超时的命令 hook 让工具调用继续。此事件的默认超时为 30 秒。PreModelSwitch 仅运行 command、http 和 mcp_tool hooks,因此 prompt 和 agent 默认不适用。

退出代码不是 0 或 2 且不打印 JSON 决策的 hook 不阻止:Claude Code 显示其 stderr 并应用切换,如 其他退出代码 下所述。

PostModelSwitch

在会话的模型更改后运行。使用它来给 Claude 模型特定的指导,而不编辑每个 CLAUDE.md,例如仅在某些模型上适用的组织范围指令。

PostModelSwitch 需要 Claude Code v2.1.251 或更高版本。它无法阻止,因为模型已经更改。Claude Code 在这些更改后运行 PostModelSwitch hooks:

  • 您或客户端请求的切换
  • 自动模型回退,改变会话的模型
  • 设置(如 opusplan)进入或离开 plan mode
  • Claude Code 恢复会话时恢复模型

当 回退模型链 中的模型服务回合时,Claude Code 不运行 PostModelSwitch hooks,因为该替换持续一个回合并保持会话的模型不变。

匹配器遵循与 PreModelSwitch 相同的规则:Claude Code 将其与会话切换到的模型的规范名称进行比较。

此示例在会话的模型更改为任何 Opus 模型时添加指导:

{
  "hooks": {
    "PostModelSwitch": [
      {
        "matcher": ".*opus.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"
          }
        ]
      }
    ]
  }
}

要确认 hook 有效,从运行不同模型的会话切换到 Opus 模型,例如从 Sonnet 会话运行 /model opus,然后询问 Claude 它对当前模型有什么指导。

PostModelSwitch 输入

PostModelSwitch hooks 接收与 PreModelSwitch 相同的字段,hook_event_name 设置为 "PostModelSwitch" 和两个更多 source 值:"auto" 用于自动回退或 Claude Code 自己进行的其他更改,以及 "resume" 用于恢复会话时恢复的模型。

当 source 为 "auto" 时,requested_model 为 null。当 source 为 "resume" 时,它是 Claude Code 恢复的保存模型设置。

PostModelSwitch 决策控制

Claude Code 在切换后的下一个请求中获取您的 hook 的 纯文本 stdout 退出 0,或 JSON 输出中的 additionalContext,并将其传递给 Claude。除了所有 hooks 可用的 JSON 输出字段 外,您可以返回:

字段 描述
additionalContext 与下一个请求一起添加到 Claude 上下文的字符串。有关详细信息,请参阅 为 Claude 添加上下文

如果 hook 在您发送下一个提示后五秒内未完成,Claude Code 发送该请求而不输出,并将其附加到以下请求。如果模型在下一个请求之前更改多次,Claude Code 仅传递最后一个切换目标模型的输出。

SessionEnd

在 Claude Code 会话结束时运行。对于清理任务、记录会话统计或保存会话状态很有用。支持匹配器以按退出原因过滤。

reason 字段在 hook 输入中指示会话为什么结束:

原因 描述
clear 会话使用 /clear 命令清除
resume 会话通过交互式 /resume 切换
logout 用户登出
prompt_input_exit 用户在提示输入可见时退出
other 其他退出原因
bypass_permissions_disabled 在 v2.1.234 中删除;Claude Code 不发送它。从您的 SessionEnd 匹配器中删除它

SessionEnd 输入

除了 常见输入字段 外,SessionEnd hooks 接收指示会话为什么结束的 reason 字段。有关所有值,请参阅上面的 原因表。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionEnd",
  "reason": "other"
}

SessionEnd hooks 没有决策控制。它们无法阻止会话终止,但可以执行清理任务。Claude Code 丢弃它们的 JSON 输出字段,如 systemMessage。

SessionEnd hooks 的默认超时为 1.5 秒。当您退出、运行 /clear 或使用交互式 /resume 切换会话时适用。您可以通过两种方式给 hook 更多时间:

  • 每个 hook timeout:在该 hook 的配置中设置 timeout。整体预算自动上升以匹配您设置文件中最高的每个 hook timeout,最多 60 秒。如果您以这种方式提高预算,没有自己的 timeout 的 hook 仍然保持默认值。在插件提供的 hooks 上设置的超时不提高预算。
  • CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS:以毫秒为单位设置此环境变量以显式覆盖预算。您设置的值也成为每个没有自己的 timeout 的 hook 的超时。

此示例将预算设置为 5 秒:

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

在 v2.1.268 之前,CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 仅提高整体预算,没有自己的 timeout 的 hook 在 1.5 秒后仍然被取消。

Elicitation

在 MCP 服务器请求用户输入中任务时运行。默认情况下,Claude Code 显示交互式对话供用户响应。Hooks 可以拦截此请求并以编程方式响应,完全跳过对话。

匹配器字段与 MCP 服务器名称匹配。

Elicitation 输入

除了 常见输入字段 外,Elicitation hooks 接收 mcp_server_name、message 和可选的 mode、url、elicitation_id 和 requested_schema 字段。

对于表单模式引出,最常见的情况:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please provide your credentials",
  "mode": "form",
  "requested_schema": {
    "type": "object",
    "properties": {
      "username": { "type": "string", "title": "Username" }
    }
  }
}

对于 URL 模式引出,用于基于浏览器的身份验证:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please authenticate",
  "mode": "url",
  "url": "https://auth.example.com/login"
}

Elicitation 输出

要以编程方式响应而不显示对话,返回一个带有 hookSpecificOutput 的 JSON 对象:

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}
字段 值 描述
action accept、decline、cancel 是否接受、拒绝或取消请求
content object 要提交的表单字段值。仅在 action 为 accept 时使用

退出代码 2 拒绝引出。Claude Code 不在任何地方显示您的 stderr 消息。

Claude Code 从 Elicitation hook 的 JSON 输出中作用于 hookSpecificOutput,并丢弃 systemMessage 和 continue。

ElicitationResult

在用户响应 MCP 引出后运行。Hooks 可以观察、修改或阻止响应,然后将其发送回 MCP 服务器。

匹配器字段与 MCP 服务器名称匹配。

ElicitationResult 输入

除了 常见输入字段 外,ElicitationResult hooks 接收 mcp_server_name、action 和可选的 mode、elicitation_id 和 content 字段。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ElicitationResult",
  "mcp_server_name": "my-mcp-server",
  "action": "accept",
  "content": { "username": "alice" },
  "mode": "form",
  "elicitation_id": "elicit-123"
}

ElicitationResult 输出

要覆盖用户的响应,返回一个带有 hookSpecificOutput 的 JSON 对象:

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline",
    "content": {}
  }
}
字段 值 描述
action accept、decline、cancel 覆盖用户的操作
content object 覆盖表单字段值。仅在 action 为 accept 时有意义

退出代码 2 阻止响应,将有效操作更改为 decline。Claude Code 不在任何地方显示您的 stderr 消息。

Claude Code 从 ElicitationResult hook 的 JSON 输出中作用于 hookSpecificOutput,并丢弃 systemMessage 和 continue。

基于提示的 hooks

除了命令、HTTP 和 MCP tool hooks 外,Claude Code 还支持基于提示的 hooks(type: "prompt"),使用 LLM 来评估是否允许或阻止操作,以及代理 hooks(type: "agent"),生成具有工具访问权限的代理验证器。并非所有事件都支持每种 hook 类型。

支持所有五种 hook 类型(command、http、mcp_tool、prompt 和 agent)的事件:

  • PermissionDenied
  • PostToolBatch
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • TeammateIdle
  • UserPromptExpansion
  • UserPromptSubmit

PermissionRequest 支持 command、http、mcp_tool 和 prompt hooks,但不支持 agent hooks。如果您在此事件上配置代理 hook,Claude Code 会跳过它,权限流程保持不变。要从 hook 允许或拒绝,请从命令或 HTTP hook 返回决定对象。

支持 command、http 和 mcp_tool hooks 但不支持 prompt 或 agent 的事件:

  • ConfigChange
  • CwdChanged
  • DirectoryAdded
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • MessageDisplay
  • Notification
  • PostCompact
  • PostModelSwitch
  • PreCompact
  • PreModelSwitch
  • SessionEnd
  • StopFailure
  • SubagentStart
  • WorktreeCreate
  • WorktreeRemove

SessionStart 和 Setup 支持 command 和 mcp_tool hooks,MCP tool hook 字段描述了它们的 mcp_tool hooks 何时运行。它们不支持 http、prompt 或 agent hooks。

基于提示的 hooks 如何工作

基于提示的 hooks 不执行 Bash 命令,而是:

  1. 将 hook 输入和您的提示发送到 Claude 模型,默认为 Haiku
  2. LLM 使用包含决定的结构化 JSON 响应
  3. Claude Code 自动处理决定

提示 hook 配置

将 type 设置为 "prompt" 并提供 prompt 字符串而不是 command。使用 $ARGUMENTS 占位符将 hook 的 JSON 输入数据注入到您的提示文本中。

此 Stop hook 要求 LLM 在允许 Claude 完成之前评估是否应该停止:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
          }
        ]
      }
    ]
  }
}
字段 必需 描述
type 是 必须是 "prompt"
prompt 是 要发送给 LLM 的提示文本。使用 $ARGUMENTS 作为 hook 输入 JSON 的占位符。如果 $ARGUMENTS 不存在,输入 JSON 被追加到提示
model 否 用于评估的模型。默认为快速模型
timeout 否 超时(秒)。默认值:30
continueOnBlock 否 在适用的事件上,true 将 ok: false 原因反馈给 Claude 并继续而不是结束转轮。默认值:false。有关每个事件的行为,请参阅响应架构

响应架构

LLM 必须使用包含以下内容的 JSON 响应:

{
  "ok": true | false,
  "reason": "Explanation for the decision",
  "impossible": true | false
}
字段 描述
ok true 允许。对于 false,请参阅下面的每个事件行为
reason 当 ok 为 false 时必需
impossible 可选。当模型判断条件永远无法满足时,模型使用 ok: false 返回它。在 Stop 和 SubagentStop 上,Claude Code 随后让转轮结束而不是反馈原因。代理 hooks 和其他事件忽略它

ok: false 时发生的情况取决于事件:

  • Stop 和 SubagentStop:原因被反馈给 Claude 作为其下一条指令,转轮继续,除非响应也设置 impossible: true,在这种情况下 Claude Code 允许停止,转轮结束
  • PreToolUse:工具调用被拒绝;默认情况下转轮结束,拒绝原因在聊天中显示为警告行。设置 continueOnBlock: true 以改为将原因返回给 Claude 作为工具错误,以便它可以调整并继续,等同于命令 hook 的 permissionDecision: "deny"。在 v2.1.210 之前,拒绝原因被返回给 Claude 作为工具错误,转轮继续
  • PostToolUse:默认情况下转轮结束,原因在聊天中显示为警告行。设置 continueOnBlock: true 以将原因反馈给 Claude 并继续转轮
  • PostToolBatch、UserPromptSubmit 和 UserPromptExpansion:转轮结束,原因显示为警告行。这些事件在 decision: "block" 上结束转轮,无论 continue 如何
  • PostToolUseFailure 和 TaskCreated:原因作为工具错误返回给 Claude,转轮继续,无论 continueOnBlock 如何
  • TaskCompleted:当任务在转轮期间被标记为完成时触发时,原因作为工具错误返回给 Claude,转轮继续,无论 continueOnBlock 如何。当它因队友停止而触发时,它的行为类似于 TeammateIdle 并默认停止队友
  • TeammateIdle:默认情况下队友停止,原因显示为警告行。设置 continueOnBlock: true 以将原因反馈给队友并保持其继续工作
  • PermissionRequest:ok: false 无效。要从 hook 拒绝批准,请使用命令 hook,返回 hookSpecificOutput.decision.behavior: "deny"
  • PermissionDenied:ok: false 无效,因为拒绝已经发生。此事件读取的唯一输出是 hookSpecificOutput.retry,提示和代理 hooks 无法设置。它们在此事件上运行,但其输出被丢弃。使用命令 hook返回 retry

如果您需要对任何事件进行更精细的控制,请使用命令 hook,其中包含决定控制中描述的每个事件字段。

检查多个条件后再停止

此 Stop hook 使用详细提示检查三个条件,然后允许 Claude 停止。SubagentStop hooks 使用相同的格式来评估子代理是否应该停止。如果模型因条件尚未满足而返回 "ok": false,Claude 继续工作,提供的原因作为其下一条指令:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

基于代理的 hooks

基于代理的 hooks(type: "agent")类似于基于提示的 hooks,但具有多轮工具访问。代理 hook 生成一个可以读取文件、搜索代码和检查代码库以验证条件的 subagent,而不是单个 LLM 调用。代理 hooks 支持与基于提示的 hooks 相同的事件,除了 PermissionRequest。

基于代理的 hooks 如何工作

当代理 hook 触发时:

  1. Claude Code 生成一个 subagent,带有您的提示和 hook 的 JSON 输入
  2. Subagent 可以使用 Read、Grep 和 Glob 等工具进行调查
  3. 在最多 50 轮后,subagent 返回结构化的 { "ok": true/false } 决定
  4. Claude Code 允许该操作(如果 ok 是 true)。如果 ok 是 false,Claude Code 处理阻止的方式与提示 hook 在该事件上具有 continueOnBlock: true 的方式相同,如响应架构下所列

代理 hooks 在验证需要检查实际文件或测试输出时很有用,而不仅仅是单独评估 hook 输入数据。

代理 hook 配置

将 type 设置为 "agent" 并提供 prompt 字符串,使用 $ARGUMENTS 作为 hook 输入 JSON 的占位符。配置字段与提示 hooks相同,除了代理 hooks 具有更长的默认超时时间 60 秒,并且没有 continueOnBlock 字段。

响应架构是 { "ok": true } 允许或 { "ok": false, "reason": "..." } 阻止。在 ok: false 时,Claude Code 处理代理 hook 的方式与处理同一事件上具有 continueOnBlock: true 的提示 hook 的方式相同;代理 hooks 没有 continueOnBlock 字段,并且不支持提示 hook 的 impossible 字段。

此 Stop hook 验证所有单元测试通过,然后允许 Claude 完成:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

在后台运行 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 执行。脚本完成时,其输出在下一个对话轮次上传递:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/run-tests.sh",
            "async": true
          }
        ]
      }
    ]
  }
}

一旦异步 hook 在后台运行,Claude Code 不会对其强制执行 timeout。Claude Code 仍然对使用 asyncRewake 运行的 hook 强制执行 timeout。

Claude Code 仅在会话运行时传递异步 hook 的结果:

  • 在非交互模式中使用 -p 标志,Claude Code 在拆卸时杀死任何仍在运行的异步 hook,并以 cancelled 结果完成它
  • 如果你的 hook 的工作必须超越 claude -p 会话,从它启动一个完全分离的进程

异步 Hooks 如何执行

当异步 hook 触发时,Claude Code 启动 hook 进程并立即继续,不等待其完成。Hook 通过 stdin 接收与同步 hook 相同的 JSON 输入。

后台进程退出后,Claude Code 从 hook 的 JSON 响应中传递 additionalContext 和 systemMessage 字段给 Claude 在下一个对话轮次。与同步 hook 的 systemMessage 不同,这两个字段都不会显示给你。

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 使其可执行:

#!/bin/bash
# run-tests-async.sh

# 从 stdin 读取 hook 输入
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# 仅对源文件运行测试
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

# 运行测试并通过 additionalContext 向 Claude 报告结果
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  MSG="Tests passed after editing $FILE_PATH"
else
  MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'

然后将此配置添加到项目根目录中的 .claude/settings.json。async: true 标志让 Claude 在测试运行时继续工作:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
            "args": [],
            "async": true
          }
        ]
      }
    ]
  }
}

限制

异步 hooks 与同步 hooks 相比有额外的约束:

  • Hook 输出在下一个对话轮次传递。如果会话空闲,响应等待直到下一个用户交互。例外:退出代码为 2 的 asyncRewake hook 即使在会话空闲时也会立即唤醒 Claude。
  • 每次执行创建一个单独的后台进程。同一异步 hook 的多个触发之间没有去重。

安全考虑

免责声明

工作区信任

Claude Code 在运行来自设置文件的任何 hook 之前会检查工作区信任。什么被视为受信任取决于会话类型:

  • 交互式会话:Claude Code 会保留来自每个设置文件的 hooks,包括您自己的 ~/.claude/settings.json,直到您为该文件夹接受工作区信任对话框,或为其信任扩展到的父目录接受
  • -p 或 SDK 会话:Claude Code 从不显示对话框,并将该文件夹视为受信任的,因此在您从未信任过的文件夹中运行存储库的 .claude/settings.json 中提交的 hooks

在您对存储库进行脚本化 claude -p 之前,如果您没有编写该存储库,请审查其 .claude/ 设置文件,使用 --bare 开始,或为该运行关闭 hooks,使用 --settings '{"disableAllHooks": true}'。项目子代理中的 Frontmatter hooks 遵循比设置文件 hooks 更严格的规则。在您信任文件夹之前运行的内容按会话类型列出每种存储库内容。

安全最佳实践

编写 hooks 时请记住这些实践:

  • 验证和清理输入:永远不要盲目信任输入数据
  • 始终引用 shell 变量:使用 "$VAR" 而不是 $VAR
  • 阻止路径遍历:检查文件路径中的 ..
  • 使用绝对路径:为脚本指定完整路径。在 exec 形式中,使用 ${CLAUDE_PROJECT_DIR} 且路径无需引用。在 shell 形式中,将其包装在双引号中
  • 跳过敏感文件:避免 .env、.git/、密钥等

Windows PowerShell 工具

在 Windows 上,您可以通过在命令 hook 上设置 "shell": "powershell" 在 PowerShell 中运行单个 hooks。Claude Code 自动检测 pwsh.exe(PowerShell 7 及更高版本的可执行文件),并回退到 powershell.exe(Windows PowerShell 5.1)。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'File written'"
          }
        ]
      }
    ]
  }
}

要从 PowerShell shell 形式命令引用项目根目录,请写入 ${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: 形式运行项目脚本,该形式在每个版本上都有效:

{
  "type": "command",
  "shell": "powershell",
  "command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\check.ps1\""
}

调试 hooks

Hook 执行详细信息被写入调试日志文件。使用 claude --debug-file <path> 启动 Claude Code 以将日志写入已知位置,或运行 claude --debug 并在 ~/.claude/debug/<session-id>.txt 读取日志。--debug 标志不打印到终端。

例如,在 Write 上的 PostToolUse hook,其命令打印 hook-ran 会产生如下条目:

2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"

对于更细粒度的 hook 匹配详细信息,设置 CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose 以查看额外的日志行,例如 hook 匹配器计数和查询匹配。

有关故障排除常见问题,如 hooks 不触发、Stop hooks 持续阻止或配置错误,请参阅指南中的限制和故障排除。有关涵盖 /context、/doctor 和设置优先级的更广泛的诊断演练,请参阅调试你的配置。