这是 Claude Code Hooks 的完整 API 参考:涵盖 30 多个生命周期事件的触发时机、JSON 输入输出格式、退出码(0/2/其他)行为、matcher 匹配规则、五种钩子类型(command/http/mcp_tool/prompt/agent)的配置字段,以及 async/prompt/agent 等高级钩子的实现细节。配置定义在 JSON 设置文件中,支持按工具名、命令内容、事件来源等条件精细过滤。管理员可通过 allowManagedHooksOnly 限制用户自定义钩子。

Claude Code Hooks 事件参考与配置指南

Claude Code Hooks 参考文档:Hook 事件、配置 schema、JSON 输入/输出格式、退出码、异步 Hook、HTTP Hook、Prompt Hook 和 MCP Tool Hook。

入门指南与示例见 用 Hooks 自动化工作流

Hooks 是用户定义的 shell 命令、HTTP 端点或 LLM 提示,在 Claude Code 生命周期的特定节点自动执行。用本文查询事件 schema、配置选项、JSON 输入/输出格式,以及异步 Hook、HTTP Hook、MCP Tool Hook 等高级功能。首次配置 Hooks 建议先看指南

Hook 生命周期

Hooks 在 Claude Code 会话的特定节点触发。事件触发且 matcher 匹配时,Claude Code 会将事件的 JSON 上下文传给 Hook 处理器。对 command Hook,输入通过 stdin 传入;对 HTTP Hook,输入作为 POST 请求体传入。处理器可以检查输入、执行操作,也可选择返回决策。事件分三种节奏:每个会话一次(SessionStart, SessionEnd),每轮一次(UserPromptSubmit, Stop, StopFailure),以及代理循环中每次工具调用(PreToolUse, PostToolUse):

事件 触发时机
SessionStart 会话开始或恢复时
Setup --init-only、或 -p 模式下的 --init/--maintenance 启动 Claude Code 时。用于 CI 或脚本中的一次性准备
UserPromptSubmit 用户提交提示后、Claude 处理前
UserPromptExpansion 用户键入的命令展开为提示、到达 Claude 前。可阻断展开
PreToolUse 工具调用执行前。可阻断
PermissionRequest 权限弹窗出现时
PermissionDenied 工具调用被自动模式分类器拒绝时。返回 {retry: true} 可告诉模型可重试被拒调用
PostToolUse 工具调用成功后
PostToolUseFailure 工具调用失败后
PostToolBatch 一批并行工具调用全部解析后、下一次模型调用前
Notification Claude Code 发送通知时
SubagentStart 子代理被启动时
SubagentStop 子代理完成时
TaskCreated 通过 TaskCreate 创建任务时
TaskCompleted 任务被标记为完成时
Stop Claude 完成响应时
StopFailure 轮次因 API 错误结束时。输出和退出码被忽略
TeammateIdle 代理团队成员即将进入空闲状态时
InstructionsLoaded CLAUDE.md.claude/rules/*.md 文件加载到上下文时。在会话开始时和文件被懒加载时触发
ConfigChange 会话期间配置文件发生变化时
CwdChanged 工作目录改变时(例如 Claude 执行 cd 命令时)。适用于 direnv 等响应式环境管理
FileChanged 被监视的文件在磁盘上变化时。matcher 字段指定要监视的文件名
WorktreeCreate 通过 --worktreeisolation: "worktree" 创建 worktree 时。替换默认 git 行为
WorktreeRemove 会话退出或子代理完成时移除 worktree 时
PreCompact 上下文压缩前
PostCompact 上下文压缩完成后
Elicitation MCP 服务器在工具调用期间请求用户输入时
ElicitationResult 用户响应 MCP elicitation 后、响应发送回服务器前
SessionEnd 会话终止时

Hook 如何解析

考虑以下拦截破坏性 shell 命令的 PreToolUse Hook。matcher 缩小到 Bash 工具调用,if 条件进一步缩小到 Bash 子命令匹配 rm *,因此 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"

#!/bin/bash
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  # 无决策;正常权限流程
fi

假设 Claude Code 决定运行 Bash "rm -rf /tmp/build"。过程如下:

  1. PreToolUse 事件触发。Claude Code 将工具输入作为 JSON 通过 stdin 发送给 Hook:

    { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
  2. matcher "Bash" 匹配工具名,激活此 Hook 组。省略 matcher 或使用 "*" 则每次事件触发都激活。

  3. if 条件 "Bash(rm *)" 匹配(rm -rf /tmp/buildrm * 的子命令),因此此处理器启动。如果命令是 npm test,则 if 检查失败,block-rm.sh 不会运行,避免进程启动开销。if 字段可选;省略时,匹配组内的所有处理器都运行。

  4. 脚本检查完整命令,发现 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 读取 JSON 决策,阻断工具调用,向 Claude 显示原因。

配置部分记录了完整 schema,每个 Hook 事件部分记录了命令接收的输入和可返回的输出。

配置

Hooks 在 JSON 设置文件中定义。配置分三层嵌套:

  1. 选择要响应的 Hook 事件,如 PreToolUseStop
  2. 添加 matcher 组 以过滤触发时机,如 “仅对 Bash 工具”
  3. 匹配时定义要运行的一个或多个 Hook 处理器

见上方 Hook 如何解析 的完整带注释的例子。

此页面使用特定术语表示每个层级:Hook 事件指生命周期节点,matcher 组指过滤器,Hook 处理器指运行的 shell 命令、HTTP 端点、MCP 工具、提示或代理。“Hook” 本身指一般功能。

Hook 位置

定义 Hook 的位置决定了作用域:

位置 作用域 可共享
~/.claude/settings.json 你的所有项目 否,仅本地
.claude/settings.json 单个项目 是,可提交到仓库
.claude/settings.local.json 单个项目 否,已被 gitignore
托管策略设置 组织级别 是,管理员控制
插件 hooks/hooks.json 启用插件时 是,随插件捆绑
技能代理 的 frontmatter 组件激活期间 是,定义在组件文件中

设置文件解析详情见设置。企业管理员可用 allowManagedHooksOnly 阻止用户、项目和插件 Hook。托管设置 enabledPlugins 中强制启用的插件 Hook 豁免,因此管理员可通过组织 marketplace 分发经过审核的 Hook。见 Hook 配置

Matcher 匹配模式

matcher 字段过滤 Hook 触发时机。matcher 的评估方式取决于其包含的字符:

Matcher 值 评估方式 示例
"*", "", 或省略 匹配所有 每次事件触发都启动
仅含字母、数字、_ 和 ` ` 精确字符串,或 `
包含任何其他字符 JavaScript 正则表达式 ^Notebook 匹配以 Notebook 开头的任何工具;mcp__memory__.* 匹配来自 memory 服务器的每个工具

FileChanged 事件在构建监视列表时不遵循这些规则。见 FileChanged

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

事件 matcher 过滤的内容 示例 matcher 值
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied 工具名称 Bash, `Edit
SessionStart 会话启动方式 startup, resume, clear, compact
Setup 触发设置的 CLI 标志 init, maintenance
SessionEnd 会话结束原因 clear, resume, logout, prompt_input_exit, bypass_permissions_disabled, other
Notification 通知类型 permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_complete, elicitation_response
SubagentStart 代理类型 general-purpose, Explore, Plan, 或自定义代理名称
PreCompact, PostCompact 压缩触发原因 manual, auto
SubagentStop 代理类型 SubagentStart 相同值
ConfigChange 配置来源 user_settings, project_settings, local_settings, policy_settings, skills
CwdChanged 不支持 matcher 每次目录更改都触发
FileChanged 要监视的 literal 文件名(见 FileChanged `.envrc
StopFailure 错误类型 rate_limit, authentication_failed, oauth_org_not_allowed, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, unknown
InstructionsLoaded 加载原因 session_start, nested_traversal, path_glob_match, include, compact
UserPromptExpansion 命令名称 你的技能或命令名称
Elicitation MCP 服务器名称 你配置的 MCP 服务器名称
ElicitationResult MCP 服务器名称 Elicitation 相同值
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove 不支持 matcher 每次触发都启动

matcher 针对 Claude Code 通过 stdin 发送给 Hook 的 JSON 输入 中的字段进行匹配。对于工具事件,该字段是 tool_name。每个 Hook 事件 部分列出了 matcher 值和该事件的输入 schema。

以下示例仅在 Claude 写入或编辑文件时运行 lint 脚本:

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

UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemoveCwdChanged 不支持 matcher,每次触发都启动。为这些事件添加 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 服务器的 create entities 工具
  • mcp__filesystem__read_file: Filesystem 服务器的 read file 工具
  • mcp__github__search_repositories: GitHub 服务器的 search 工具

要匹配来自某个服务器的所有工具,在服务器前缀后追加 .*.* 是必需的:像 mcp__memory 这样的 matcher 仅包含字母和下划线,因此会被当作精确字符串比较,不会匹配任何工具。

  • mcp__memory__.* 匹配 memory 服务器的所有工具
  • mcp__.*__write.* 匹配来自任何服务器、名称以 write 开头的工具

此示例记录所有 memory 服务器操作,并验证来自任何 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 处理器:matcher 匹配时运行的 shell 命令、HTTP 端点、MCP 工具、LLM 提示或代理。有五种类型:

  • Command Hook (type: "command"): 运行 shell 命令。脚本通过 stdin 接收事件的 JSON 输入,通过退出码和 stdout 传回结果。
  • HTTP Hook (type: "http"): 将事件的 JSON 输入作为 HTTP POST 请求发送到 URL。端点通过响应体传回结果,使用与 command Hook 相同的 JSON 输出格式
  • MCP Tool Hook (type: "mcp_tool"): 调用已连接 MCP 服务器 上的一个工具。工具的文本输出被当作 command Hook 的 stdout 处理。
  • Prompt Hook (type: "prompt"): 向 Claude 模型发送提示进行单轮评估。模型以 JSON 格式返回 yes/no 决策。见 Prompt-based Hooks
  • Agent Hook (type: "agent"): 启动一个可使用 Read、Grep、Glob 等工具的代理来验证条件,再返回决策。Agent Hook 是实验性功能,未来可能变更。见 Agent-based Hooks

通用字段

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

字段 必需 描述
type "command", "http", "mcp_tool", "prompt", 或 "agent"
if 权限规则语法,用于过滤此 Hook 何时运行,例如 "Bash(git *)""Edit(*.ts)"。仅当工具调用匹配模式时,或 Bash 命令过于复杂无法解析时,Hook 才启动。仅对工具事件求值:PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied。其他事件上,设置了 if 的 Hook 从不运行。使用与权限规则相同的语法
timeout 超时秒数。默认值:command, httpmcp_tool 为 600;prompt 为 30;agent 为 60。UserPromptSubmitcommand, httpmcp_tool 的默认值降低为 30
statusMessage Hook 运行时显示的自定义 spinner 消息
once 如果为 true,每个会话仅运行一次然后被移除。仅对声明在技能 frontmatter中的 Hook 生效;在设置文件和代理 frontmatter 中被忽略

if 字段只包含一条权限规则。没有用于组合规则的 &&, || 或列表语法;要应用多个条件,为每个条件定义一个单独的 Hook 处理器。对于 Bash,规则在去除前导 VAR=value 赋值后,针对工具输入的每个子命令进行匹配,因此 if: "Bash(git push *)" 既匹配 FOO=bar git push 也匹配 npm test && git push。Hook 在任一子命令匹配时运行,并且当命令过于复杂无法解析时总是运行。

Command Hook 字段

通用字段外,command Hook 还接受以下字段:

字段 必需 描述
command 要执行的 shell 命令。如果提供 args,则直接启动的可执行文件。见 Exec 形式和 Shell 形式
args 参数列表。如果存在,command 被解析为可执行文件并用 args 作为参数向量直接启动,不涉及 shell。见 Exec 形式和 Shell 形式
async 如果为 true,在后台运行而不阻塞。见在后台运行 Hook
asyncRewake 如果为 true,在后台运行并在退出码为 2 时唤醒 Claude。隐含 async。Hook 的 stderr(如果 stderr 为空则用 stdout)作为系统提醒显示给 Claude,使其能对长时间运行的后台失败做出反应
shell 此 Hook 使用的 shell。接受 "bash"(默认)或 "powershell"。设置为 "powershell" 时通过 Windows 上的 PowerShell 运行命令。不要求设置 CLAUDE_CODE_USE_POWERSHELL_TOOL,因为 Hooks 直接启动 PowerShell。设置 args 时被忽略
Exec 形式和 Shell 形式

Command Hook 在设置了 args 时以 exec 形式运行,省略 args 时以 shell 形式运行。只要 Hook 引用路径占位符,就应设置 args,因为每个元素作为一个参数传递,无需引用。当需要 shell 功能(如管道或 &&)或两者都无关时,省略 args

Exec 形式args 存在时运行。Claude Code 将 command 解析为 PATH 上的可执行文件,并用 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 对字符串进行分词、展开变量、解释管道、&&、重定向和通配符。

Windows 上,exec 形式要求 command 解析为真正的可执行文件,如 .exe。npm、npx、eslint 等工具安装在 node_modules/.bin 中的 .cmd.bat 存根不是可执行文件,不能在无 shell 的情况下启动。要以 exec 形式运行它们,直接用 node 调用底层脚本,例如 "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/node_modules/eslint/bin/eslint.js"]node + 脚本路径的模式在所有平台上都有效,因为 node.exe 是真正的二进制文件。要通过名称运行 .cmd.bat 存根,使用 shell 形式。

此示例运行随插件捆绑的 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_DIRCLAUDE_PLUGIN_ROOTCLAUDE_PLUGIN_DATA 导出到启动的进程上,因此无论脚本如何启动,都能读取 process.env.CLAUDE_PLUGIN_ROOT。插件 Hook 还会替换 ${user_config.*} 值;见用户配置

在 exec 形式中,command 只能是可执行文件名或路径。如果 command 是不含路径分隔符的裸名称,且包含空格并与 args 同时使用,Claude Code 会记录警告,因为启动会失败:不存在名为 node script.js 的可执行文件。将多余 token 移到 args 中。包含空格的绝对路径,如 C:\Program Files\nodejs\node.exe,是单个有效的可执行文件,不会触发警告。

HTTP Hook 字段

通用字段外,HTTP Hook 还接受以下字段:

字段 必需 描述
url 发送 POST 请求的 URL
headers 额外的 HTTP 头,键值对。值支持使用 $VAR_NAME${VAR_NAME} 语法进行环境变量插值。仅 allowedEnvVars 中列出的变量会被解析
allowedEnvVars 可在头部值中插值的环境变量名列表。对未列出变量的引用被替换为空字符串。任何环境变量插值若要生效,此字段必填

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

错误处理与 command Hook 不同:非 2xx 响应、连接失败和超时都会产生非阻塞错误,执行继续。要阻断工具调用或拒绝权限,返回包含 decision: "block"hookSpecificOutput 中包含 permissionDecision: "deny" 的 JSON 体的 2xx 响应。

此示例将 PreToolUse 事件发送到本地验证服务,使用 MY_TOKEN 环境变量中的 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 Tool Hook 字段

通用字段外,MCP Tool Hook 还接受以下字段:

字段 必需 描述
server 已配置 MCP 服务器的名称。服务器必须已连接;Hook 从不触发 OAuth 或连接流程
tool 要在该服务器上调用的工具名称
input 传递给工具的参数。字符串值支持从 Hook 的 JSON 输入 进行 ${path} 替换,如 "${tool_input.file_path}"

工具的文本内容被当作 command Hook 的 stdout 处理:如果它能被解析为有效的 JSON 输出,则作为决策处理,否则显示为纯文本。如果指定的服务器未连接,或工具返回 isError: true,则 Hook 产生非阻塞错误,执行继续。

MCP Tool Hook 在 Claude Code 连接到 MCP 服务器后可用于每个 Hook 事件。SessionStartSetup 通常在服务器完成连接前触发,因此这些事件上的 Hook 应在首次运行时预期"未连接"错误。

此示例在每次 WriteEdit 后调用 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}" }
          }
        ]
      }
    ]
  }
}

Prompt 和 Agent Hook 字段

通用字段外,Prompt 和 Agent Hook 还接受以下字段:

字段 必需 描述
prompt 发送给模型的提示文本。使用 $ARGUMENTS 作为 Hook 输入 JSON 的占位符
model 用于评估的模型。默认使用快速模型

所有匹配的 Hook 并行运行,相同的处理器自动去重。Command Hook 按命令字符串和 args 去重,HTTP Hook 按 URL 去重。处理器在当前目录中使用 Claude Code 的环境运行。$CLAUDE_CODE_REMOTE 环境变量在远程 Web 环境中设置为 "true",在本地 CLI 中不设置。

按路径引用脚本

使用以下占位符引用相对于项目或插件根的 Hook 脚本,无论 Hook 运行时的工作目录如何:

  • ${CLAUDE_PROJECT_DIR}: 项目根目录。
  • ${CLAUDE_PLUGIN_ROOT}: 插件的安装目录,用于随插件捆绑的脚本。每次插件更新都会变化。
  • ${CLAUDE_PLUGIN_DATA}: 插件的持久数据目录,用于应保留到插件更新的依赖和状态。

对于引用路径占位符的任何 Hook,优先使用 exec 形式。Exec 形式将每个 args 元素作为一个参数传递,无需 shell 分词,因此包含空格或特殊字符的路径无需引用。在 shell 形式中,将每个占位符括在双引号内。

此示例使用 ${CLAUDE_PROJECT_DIR} 在每次 WriteEdit 工具调用后运行项目 .claude/hooks/ 目录中的样式检查器:

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

hooks/hooks.json 中定义插件 Hook,并可选择包含顶级 description 字段。插件启用时,其 Hook 与用户和项目 Hook 合并。

此示例运行随插件捆绑的格式化脚本:

{
  "description": "Automatic code formatting",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
            "args": [],
            "timeout": 30
          }
        ]
      }
    ]
  }
}

插件组件参考了解创建插件 Hook 的详情。

Skill 和 Agent 中的 Hooks

除设置文件和插件外,Hooks 也可以直接在技能子代理中使用 frontmatter 定义。这些 Hook 限定在组件的生命周期内,仅在该组件激活时运行。

支持所有 Hook 事件。对于子代理,Stop Hook 会自动转换为 SubagentStop,因为那是子代理完成时触发的事件。

Hooks 使用与基于设置的 Hook 相同的配置格式,但限定在组件的生命周期内,并在组件完成时清理。

此技能定义了一个 PreToolUse Hook,在每个 Bash 命令前运行安全验证脚本:

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

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

/hooks 菜单

在 Claude Code 中键入 /hooks 可打开已配置 Hook 的只读浏览器。菜单显示每个 Hook 事件及其配置 Hook 的计数,允许深入查看 matcher,并显示每个 Hook 处理器的完整详情。用于验证配置、检查 Hook 来自哪个设置文件,或检查 Hook 的命令、提示或 URL。

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

  • User: 来自 ~/.claude/settings.json
  • Project: 来自 .claude/settings.json
  • Local: 来自 .claude/settings.local.json
  • Plugin: 来自插件的 hooks/hooks.json
  • Session: 为当前会话在内存中注册
  • Built-in: 由 Claude Code 内部注册

选择某个 Hook 会打开详情视图,显示其事件、matcher、类型、源文件以及完整的命令、提示或 URL。菜单是只读的:要添加、修改或删除 Hook,直接编辑设置 JSON 或让 Claude 进行更改。

禁用或删除 Hook

要删除 Hook,直接从设置 JSON 文件中删除其条目。

要临时禁用所有 Hook 而不删除,在设置文件中设置 "disableAllHooks": true。无法在保留配置的同时禁用单个 Hook。

disableAllHooks 设置遵循托管设置层级。如果管理员通过托管策略配置了 Hook,用户、项目或本地设置中的 disableAllHooks 无法禁用这些托管 Hook。只有托管设置级别的 disableAllHooks 才能禁用托管 Hook。

对设置文件中 Hook 的直接编辑通常会被文件监视器自动检测。

Hook 输入和输出

Command Hook 通过 stdin 接收 JSON 数据,并通过退出码、stdout 和 stderr 传递结果。HTTP Hook 接收相同的 JSON 作为 POST 请求体,并通过 HTTP 响应体传递结果。本节涵盖所有事件通用的字段和行为。每个 Hook 事件 下的章节包含其特定的输入 schema 和决策控制选项。

在 macOS 和 Linux 上,自 v2.1.139 起,command Hook 在其自己的会话中运行,没有控制终端。Hook 进程及其任何子进程无法打开 /dev/tty 或直接向 Claude Code 界面发送转义序列。Windows 没有 /dev/tty。要在任何平台上向用户显示消息,在 JSON 输出中返回 systemMessage。要触发桌面通知、设置窗口标题或响铃,返回 terminalSequence 代替。

通用输入字段

Hook 事件除了每个 Hook 事件 章节中记录的特定字段外,还会收到以下 JSON 字段。对于 command Hook,此 JSON 通过 stdin 到达。对于 HTTP Hook,它作为 POST 请求体到达。

字段 描述
session_id 当前会话标识符
transcript_path 对话 JSON 的路径
cwd Hook 被调用时的当前工作目录
permission_mode 当前的权限模式"default", "plan", "acceptEdits", "auto", "dontAsk", 或 "bypassPermissions"。并非所有事件都接收此字段:请查看每个事件的 JSON 示例确认
effort 对象,包含一个 level 字段,表示该轮的活跃努力级别"low", "medium", "high", "xhigh", 或 "max"。如果请求的努力超过当前模型支持的范围,这是模型实际使用的降级级别,而非你请求的级别。该对象匹配状态行effort 字段。出现在工具使用上下文内触发的事件中,如 PreToolUse, PostToolUse, Stop, SubagentStop,且当前模型支持 effort 参数时。该级别也可通过 $CLAUDE_EFFORT 环境变量提供给 Hook 命令和 Bash 工具。
hook_event_name 触发的事件的名称

使用 --agent 运行或在子代理内部运行时,还会包含两个额外字段:

字段 描述
agent_id 子代理的唯一标识符。仅在 Hook 在子代理调用内部触发时出现。用于将子代理 Hook 调用与主线程调用区分开。
agent_type 代理名称(例如 "Explore""security-reviewer")。当会话使用 --agent 或 Hook 在子代理内部触发时出现。对于子代理,子代理的类型优先于会话的 --agent 值。对于自定义子代理,这是代理 frontmatter 中的 name 字段,而非文件名。

只有 SessionStart Hook 会收到 model 字段。没有 $CLAUDE_MODEL 环境变量。Hook 进程继承父环境,因此如果在其 shell 中设置了 $ANTHROPIC_MODEL,它可以读取该值,但该值不会在会话中通过 /model 切换模型时改变。

例如,Bash 命令的 PreToolUse Hook 会在 stdin 上收到以下内容:

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

tool_nametool_input 字段是特定于事件的。每个 Hook 事件 章节都记录了该事件的额外字段。

退出码输出

Hook 命令的退出码告诉 Claude Code 该动作是否应继续、被阻断或被忽略。

退出码 0 表示成功。Claude Code 解析 stdout 中的 JSON 输出字段。JSON 输出仅在退出码为 0 时被处理。对于大多数事件,stdout 会写入调试日志,但不会显示在转录中。例外情况是 UserPromptSubmit, UserPromptExpansionSessionStart,这些事件中 stdout 会作为上下文添加,Claude 可以看到并据此行动。

退出码 2 表示阻断错误。Claude Code 忽略 stdout 及其中的任何 JSON。相反,stderr 文本会作为错误消息反馈给 Claude。效果取决于事件:PreToolUse 阻断工具调用,UserPromptSubmit 拒绝提示,依此类推。完整列表见退出码 2 的行为(按事件)

任何其他退出码 对大多数 Hook 事件是非阻塞错误。转录中会显示 <hook name> hook error 通知,后跟 stderr 的第一行,因此无需 --debug 即可识别原因。执行继续,完整 stderr 写入调试日志。

例如,一个阻断危险 Bash 命令的 Hook 脚本:

#!/bin/bash
# 从 stdin 读取 JSON 输入,检查命令
command=$(jq -r '.tool_input.command' < /dev/stdin)

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # 阻断错误:阻止工具调用
fi

exit 0  # 无决策:正常权限流程

对于大多数 Hook 事件,只有退出码 2 会阻断动作。Claude Code 将退出码 1 视为非阻塞错误,并继续执行该动作,即使 1 是传统的 Unix 失败码。如果你的 Hook 旨在执行策略,请使用 exit 2。例外情况是 WorktreeCreate,其中任何非零退出码都会中止 worktree 创建。

退出码 2 的行为(按事件)

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

Hook 事件 可阻断? 退出码 2 时发生什么
PreToolUse 阻断工具调用
PermissionRequest 拒绝权限
UserPromptSubmit 阻断提示处理并擦除提示
UserPromptExpansion 阻断展开
Stop 阻止 Claude 停止,继续对话
SubagentStop 阻止子代理停止
TeammateIdle 阻止队友进入空闲状态(队友继续工作)
TaskCreated 回滚任务创建
TaskCompleted 阻止任务被标记为完成
ConfigChange 阻止配置更改生效(policy_settings 除外)
StopFailure 输出和退出码被忽略
PostToolUse 向 Claude 显示 stderr(工具已运行)
PostToolUseFailure 向 Claude 显示 stderr(工具已失败)
PostToolBatch 在下次模型调用前停止代理循环
PermissionDenied 退出码和 stderr 被忽略(拒绝已发生)。使用 JSON hookSpecificOutput.retry: true 告诉模型可重试
Notification 仅向用户显示 stderr
SubagentStart 仅向用户显示 stderr
SessionStart 仅向用户显示 stderr
Setup 仅向用户显示 stderr
SessionEnd 仅向用户显示 stderr
CwdChanged 仅向用户显示 stderr
FileChanged 仅向用户显示 stderr
PreCompact 阻断压缩
PostCompact 仅向用户显示 stderr
Elicitation 拒绝 elicitation
ElicitationResult 阻断响应(动作变为 decline)
WorktreeCreate 任何非零退出码都会导致 worktree 创建失败
WorktreeRemove 失败仅在调试模式下记录
InstructionsLoaded 退出码被忽略

HTTP 响应处理

HTTP Hook 使用 HTTP 状态码和响应体,而非退出码和 stdout:

  • 2xx 且空体: 成功,等同于退出码 0 且无输出
  • 2xx 且纯文本体: 成功,文本作为上下文添加
  • 2xx 且 JSON 体: 成功,使用与 command Hook 相同的 JSON 输出 schema 解析
  • 非 2xx 状态: 非阻塞错误,执行继续
  • 连接失败或超时: 非阻塞错误,执行继续

与 command Hook 不同,HTTP Hook 不能仅通过状态码发出阻断错误信号。要阻断工具调用或拒绝权限,返回包含适当决策字段的 JSON 体的 2xx 响应。

JSON 输出

退出码只能让你阻断或保持沉默,但 JSON 输出提供了更细粒度的控制。不退出码 2 阻断,而应退出码 0 并向 stdout 打印一个 JSON 对象。Claude Code 从该 JSON 中读取特定字段来控制行为,包括决策控制中的阻断、允许或升级给用户。

每个 Hook 必须选择一种方法,不能同时使用:要么仅使用退出码进行信号传递,要么退出码 0 并打印 JSON 进行结构化控制。Claude Code 仅在退出码 0 时处理 JSON。如果退出码 2,任何 JSON 都会被忽略。

Hook 的 stdout 必须只包含 JSON 对象。如果 shell 配置在启动时打印文本,可能会干扰 JSON 解析。见故障排除指南中的 “JSON 验证失败”。

Hook 输出字符串,包括 additionalContext, systemMessage 和纯文本 stdout,限制为 10,000 个字符。超出此限制的输出会被保存到文件,并用预览和文件路径替换,与处理大型工具结果的方式相同。

JSON 对象支持三种字段:

  • 通用字段continue 在所有事件中工作。这些列在下表中。
  • 顶级 decisionreason 被某些事件用于阻断或提供反馈。
  • hookSpecificOutput 是一个嵌套对象,用于需要更丰富控制的事件。它需要一个设置为事件名称的 hookEventName 字段。
字段 默认值 描述
continue true 如果为 false,Claude 在 Hook 运行后完全停止处理。优先于任何事件特定的决策字段
stopReason continuefalse 时向用户显示的消息。不向 Claude 显示
suppressOutput false 如果为 true,隐藏 Hook 的 stdout 不显示在转录中。Stdout 仍出现在调试日志中
systemMessage 向用户显示的警告消息
terminalSequence Claude Code 代表你发出的终端转义序列,如桌面通知、窗口标题或铃响。限制为 OSC 0/1/2/9/99/777 和 BEL。如果值包含允许列表之外的任何内容,该字段被忽略。使用此替代直接写入 /dev/tty,该操作对 Hook 不可用

要完全停止 Claude(无论事件类型):

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

发出终端通知

terminalSequence 字段需要 Claude Code v2.1.141 或更高版本。

Hook 在没有控制终端的情况下运行,因此直接向 /dev/tty 写入转义序列会失败。相反,在 terminalSequence 字段中返回转义序列,Claude Code 会通过其自己的终端写入路径为你发出。这在 tmux 和 GNU screen 内以及没有 /dev/tty 的 Windows 上都是无竞争且有效的。

该字段接受一个或多个已允许转义序列的字符串:

  • 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,都会被拒绝,字段被忽略。

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

#!/bin/bash
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 或语言中都是相同的。Windows 上,在 PowerShell 或脚本中构建转义字符串,并发出相同的 JSON 对象。

terminalSequence 是以前直接向 /dev/tty 写入转义序列的 Hook 的受支持替代方案。允许列表限制为不能移动光标或更改颜色的序列,因此 Hook 永远不会破坏屏幕上的提示。

为 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 传递文件路径及简短预览。

使用 additionalContext 传递 Claude 应了解的当前环境状态或刚刚运行的操作的信息:

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

对于不变的指令,优先使用 CLAUDE.md。它无需运行脚本即可加载,是静态项目约定的标准位置。

将文本写为事实陈述,而非命令式系统指令。如"The deployment target is production"或"This repo uses bun test"这样的措辞读起来是项目信息。将被框架化为带外系统命令的文本可能触发 Claude 的提示注入防御,导致 Claude 向你显示文本而非将其视为上下文。

一旦注入,文本会保存在会话记录中。对于会话中的事件(如 PostToolUseUserPromptSubmit),使用 --continue--resume 恢复时会重放保存的文本,而不会为过去的轮次重新运行 Hook,因此时间戳或提交 SHA 等值在恢复时会过时。SessionStart Hook 在恢复时再次运行,source 设置为 "resume",因此它们可以刷新上下文。

决策控制

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

事件 决策模式 关键字段
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact 顶级 decision decision: "block", reason
TeammateIdle, TaskCreated, TaskCompleted Exit code 或 continue: false Exit code 2 用 stderr 反馈阻断动作。JSON {"continue": false, "stopReason": "..."} 也完全停止队友,匹配 Stop Hook 行为
PreToolUse hookSpecificOutput permissionDecision (allow/deny/ask/defer), permissionDecisionReason
PermissionRequest hookSpecificOutput decision.behavior (allow/deny)
PermissionDenied hookSpecificOutput retry: true 告诉模型可重试被拒绝的工具调用
WorktreeCreate 路径返回 Command Hook 在 stdout 上打印路径;HTTP Hook 返回 hookSpecificOutput.worktreePath。Hook 失败或缺少路径则创建失败
Elicitation hookSpecificOutput action (accept/decline/cancel), content (accept 时的表单字段值)
ElicitationResult hookSpecificOutput action (accept/decline/cancel), content (表单字段值覆盖)
SessionStart, Setup, SubagentStart 仅上下文 hookSpecificOutput.additionalContext 为 Claude 添加上下文。SessionStart 还接受 initialUserMessagewatchPaths。无阻断或决策控制
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged 无决策控制。用于副作用如日志记录或清理

以下是每种模式的示例:

顶级 decision: 由 UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChangePreCompact 使用。唯一的值是 "block"。要允许动作继续,从 JSON 中省略 decision,或退出码 0 且无任何 JSON:

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

PreToolUse hookSpecificOutput: 使用 hookSpecificOutput 进行更丰富的控制:allow、deny 或升级给用户。还可在运行前修改工具输入或为 Claude 注入额外上下文。完整选项见 PreToolUse 决策控制

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed"
  }
}

PermissionRequest hookSpecificOutput: 使用 hookSpecificOutput 代表用户允许或拒绝权限请求。允许时,还可修改工具输入或应用权限规则,以便不再提示用户。完整选项见 PermissionRequest 决策控制

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

更多扩展示例,包括 Bash 命令验证、提示过滤和自动审批脚本,见指南中的你能自动化什么以及 Bash 命令验证器参考实现

Hook 事件

每个事件对应 Claude Code 生命周期中 Hook 可以运行的一个点。以下几节按生命周期顺序排列:从会话设置到代理循环再到会话结束。每节描述了事件触发时机、支持的 matcher、接收的 JSON 输入,以及如何通过输出控制行为。

SessionStart

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

SessionStart 在每个会话上运行,因此保持这些 Hook 快速。仅支持 type: "command"type: "mcp_tool" Hook。

matcher 值对应会话的启动方式:

Matcher 触发时机
startup 新会话
resume --resume, --continue, 或 /resume
clear /clear
compact 自动或手动压缩

SessionStart 输入

通用输入字段外,SessionStart Hook 还接收 source, model 和可选的 agent_typesource 字段指示会话如何启动:"startup" 用于新会话,"resume" 用于恢复的会话,"clear"/clear 之后,"compact" 在压缩之后。model 字段包含模型标识符。如果你用 claude --agent <name> 启动 Claude Code,则包含 agent_type 字段。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "startup",
  "model": "claude-sonnet-4-6"
}

SessionStart 决策控制

Hook 脚本打印到 stdout 的任何文本都会作为 Claude 的上下文添加。除所有 Hook 可用的 JSON 输出字段外,还可以返回这些事件特定字段:

字段 描述
additionalContext 在对话开始时、第一个提示之前添加到 Claude 上下文的字符串。见为 Claude 添加上下文了解文本如何传递及应放置什么内容
initialUserMessage 用作会话第一条用户消息的字符串。适用于非交互模式 (-p),即使未提供提示,它也成为第一轮。如果提供了提示,它作为下一轮。与 additionalContext 不同,后者附加到现有轮次,而此字段创建轮次
watchPaths 此会话期间要监视的绝对路径数组,用于 FileChanged 事件
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2"
  }
}

由于此事件的纯文本 stdout 已可到达 Claude,仅加载上下文的 Hook 可以直接打印到 stdout,无需构建 JSON。需要将上下文与其他字段(如 suppressOutput)结合使用时,使用 JSON 格式。

持久化环境变量

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

要设置单个环境变量,向 CLAUDE_ENV_FILE 写入 export 语句。使用追加 (>>) 以保留其他 Hook 设置的变量:

#!/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)

# 运行修改环境的设置命令
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

写入此文件的任何变量在会话中 Claude Code 执行的所有后续 Bash 命令中都可用。

CLAUDE_ENV_FILE 对 SessionStart, Setup, CwdChangedFileChanged Hook 可用。其他 Hook 类型无法访问此变量。

Setup

仅当使用 --init-only 启动 Claude Code,或使用打印模式 (-p) 的 --init--maintenance 时触发。正常启动时不触发。用于你在 CI 或脚本中显式触发的单次依赖安装或计划性清理,与正常会话启动分开。对于每次会话的初始化,使用 SessionStart 代替。

matcher 值对应触发 Hook 的 CLI 标志:

Matcher 触发时机
init claude --init-onlyclaude -p --init
maintenance claude -p --maintenance

--init-only 运行 Setup Hook 和 matcher 为 startup 的 SessionStart Hook,然后退出而不开始对话。--init--maintenance 仅在结合 -p(打印模式)时触发 Setup Hook;在交互式会话中这两个标志当前不会触发 Setup Hook。

因为 Setup 并非每次启动都触发,需要安装依赖项的插件不能仅依赖 Setup。实用的模式是在首次使用时检查依赖项,如果缺失则安装,例如测试 ${CLAUDE_PLUGIN_DATA}/node_modules 是否存在,如果不存在则运行 npm install 的 Hook 或技能。见持久数据目录了解存储已安装依赖项的位置。

Setup 输入

通用输入字段外,Setup Hook 还接收设置为 "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 Hook 无法阻断。退出码 2 时,stderr 向用户显示;任何其他非零退出码时,stderr 仅在使用 --verbose 启动时出现。两种情况下执行都继续。要将信息传递到 Claude 的上下文中,在 JSON 输出中返回 additionalContext;纯文本 stdout 仅写入调试日志。除所有 Hook 可用的 JSON 输出字段外,还可以返回这些事件特定字段:

字段 描述
additionalContext 添加到 Claude 上下文的字符串。多个 Hook 的值会被拼接
{
  "hookSpecificOutput": {
    "hookEventName": "Setup",
    "additionalContext": "Dependencies installed: node_modules, .venv"
  }
}

Setup Hook 可以访问 CLAUDE_ENV_FILE。写入该文件的变量会像 SessionStart Hook 一样持续到会话的后续 Bash 命令中。仅支持 type: "command"type: "mcp_tool" Hook。

InstructionsLoaded

CLAUDE.md.claude/rules/*.md 文件被加载到上下文时触发。此事件在会话开始时为积极加载的文件触发,稍后当文件被懒加载时再次触发,例如当 Claude 访问包含嵌套 CLAUDE.md 的子目录或具有 paths: frontmatter 的条件规则匹配时。Hook 不支持阻断或决策控制。它出于可观察性目的异步运行。

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

InstructionsLoaded 输入

通用输入字段外,InstructionsLoaded Hook 还接收以下字段:

字段 描述
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 Hook 无决策控制。它们不能阻断或修改指令加载。将此事件用于审计日志、合规跟踪或可观察性。

UserPromptSubmit

当用户提交提示时、Claude 处理之前运行。允许根据提示/对话添加上下文、验证提示或阻断某些类型的提示。

UserPromptSubmit Hook 对于 command, httpmcp_tool 类型有 30 秒的默认超时,比这些类型在其他事件上的 600 秒默认值短。因为此 Hook 在每个提示前运行并阻塞模型处理直到完成,卡住的 Hook 会阻塞会话。如果 Hook 需要更多时间,在 Hook 条目中设置 timeout 字段。

UserPromptSubmit 输入

通用输入字段外,UserPromptSubmit Hook 还接收包含用户提交文本的 prompt 字段。

{
  "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 Hook 可以控制是否处理用户提示,并添加上下文。所有 JSON 输出字段都可用。

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

  • 纯文本 stdout: 写入 stdout 的任何非 JSON 文本都作为上下文添加
  • additionalContext 的 JSON: 使用下面的 JSON 格式以获得更多控制。additionalContext 字段更谨慎地添加

纯文本 stdout 在转录中显示为 Hook 输出。additionalContext 字段添加得更隐蔽。

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

字段 描述
decision "block" 阻止提示被处理并从上下文中擦除它。省略则允许提示继续
reason decision"block" 时向用户显示。不添加到上下文
additionalContext 与提交的提示一起添加到 Claude 上下文的字符串。见为 Claude 添加上下文
sessionTitle 设置会话标题。用于根据提示内容自动命名会话
suppressOriginalPrompt 如果 decision"block" 时此字段为 true,则向用户显示的阻止消息中省略原始提示文本
{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title"
  }
}

简单用例不需要 JSON 格式。要添加上下文,可以用退出码 0 将纯文本打印到 stdout。需要阻断提示或更结构化控制时使用 JSON。

UserPromptExpansion

当用户键入的斜杠命令展开为提示、到达 Claude 之前运行。用于阻断对特定命令的直接调用、为特定技能注入上下文,或记录用户调用了哪些命令。例如,匹配 deploy 的 Hook 可以在不存在审批文件时阻断 /deploy,或匹配审查技能的 Hook 可以将团队的审查清单追加为 additionalContext

此事件覆盖了 PreToolUse 未覆盖的路径:匹配 Skill 工具的 PreToolUse Hook 仅在 Claude 调用该工具时触发,但直接键入 /skillname 会绕过 PreToolUseUserPromptExpansion 在该直接路径上触发。

匹配 command_name。将 matcher 留空可在每个提示类型的斜杠命令上触发。

UserPromptExpansion 输入

通用输入字段外,UserPromptExpansion Hook 还接收 expansion_type, command_name, command_args, command_source 和原始 prompt 字符串。expansion_type 字段对于技能和自定义命令为 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 Hook 可以阻断展开或添加上下文。所有 JSON 输出字段都可用。

字段 描述
decision "block" 阻止斜杠命令展开。省略则允许继续
reason decision"block" 时向用户显示
additionalContext 与展开的提示一起添加到 Claude 上下文的字符串。见为 Claude 添加上下文
{
  "decision": "block",
  "reason": "This slash command is not available",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptExpansion",
    "additionalContext": "Additional context for this expansion"
  }
}

PreToolUse

在 Claude 创建工具参数之后、处理工具调用之前运行。匹配工具名称:Bash, Edit, Write, Read, Glob, Grep, Agent, WebFetch, WebSearch, AskUserQuestion, ExitPlanMode 和任何 MCP 工具名称

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

PreToolUse 输入

通用输入字段外,PreToolUse Hook 还接收 tool_name, tool_inputtool_use_idtool_input 字段取决于工具:

Bash

执行 shell 命令。

字段 类型 示例 描述
command 字符串 "npm test" 要执行的 shell 命令
description 字符串 "Run test suite" 命令作用的可选描述
timeout 数字 120000 可选超时(毫秒)
run_in_background 布尔 false 是否在后台运行命令
Write

创建或覆盖文件。

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

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

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

读取文件内容。

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

查找匹配 glob 模式的文件。

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

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

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

获取并处理 Web 内容。

字段 类型 示例 描述
url 字符串 "https://example.com/api" 获取内容的 URL
prompt 字符串 "Extract the API endpoints" 对获取内容运行的提示
WebSearch

搜索 Web。

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

启动一个子代理

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

PostToolUse 中,已完成的 Agent 调用的 tool_response 携带子代理的最终文本以及使用遥测。读取这些字段以从 Hook 记录每个子代理的成本:

字段 类型 示例 描述
status 字符串 "completed" 同步调用为 "completed"run_in_background: true"async_launched"
agentId 字符串 "a4d2c8f1e0b3a297" 子代理运行的标识符
content 数组 [{"type": "text", "text": "Found 12 endpoints..."}] 子代理的最终文本块
totalTokens 数字 12450 子代理所有轮次的总计费 token
totalDurationMs 数字 48211 子代理运行的挂钟持续时间
totalToolUseCount 数字 7 子代理进行的工具调用次数
usage 对象 {"input_tokens": 8320, ...} 按类型划分的 token 细分:input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

对于 run_in_background: true 调用,工具在启动子代理后立即返回,因此 tool_response 不携带使用字段。它包含 status: "async_launched", agentId, description, promptoutputFile

AskUserQuestion

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

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

呈现一个计划并要求用户在 Claude 离开计划模式之前批准。Claude 在调用工具前将计划写入磁盘上的文件,因此模型字面 tool_input 仅携带 allowedPrompts。Claude Code 在将输入传递给 Hook 之前注入计划内容和文件路径。

字段 类型 示例 描述
plan 字符串 "## Refactor auth\n1. Extract..." Markdown 格式的计划内容。从磁盘上的计划文件注入
planFilePath 字符串 "/Users/.../plans/refactor-auth.md" 计划文件的路径。注入
allowedPrompts 数组 [{"tool": "Bash", "prompt": "run tests"}] 可选。Claude 为实现计划而请求的基于提示的权限,每个包含 tool 名称和描述操作类别的 prompt

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

PreToolUse 决策控制

PreToolUse Hook 可以控制工具调用是否继续。与使用顶级 decision 字段的其他 Hook 不同,PreToolUse 在 hookSpecificOutput 对象内返回其决策。这提供了更丰富的控制:四种结果(allow, deny, ask, defer)加上在运行前修改工具输入的能力。

字段 描述
permissionDecision "allow" 跳过权限提示。"deny" 阻止工具调用。"ask" 提示用户确认。"defer" 优雅退出,以便稍后可恢复工具。无论 Hook 返回什么,拒绝和询问规则仍会被评估
permissionDecisionReason 对于 "allow""ask",向用户显示但不向 Claude 显示。对于 "deny",向 Claude 显示。对于 "defer",忽略
updatedInput 在运行前修改工具的输入参数。替换整个输入对象,因此将未更改的字段与已修改的字段一起包含。与 "allow" 结合以自动批准,或与 "ask" 结合向用户显示修改后的输入。对于 "defer",忽略
additionalContext 与工具结果一起添加到 Claude 上下文的字符串。当 permissionDecision"defer" 时忽略。见为 Claude 添加上下文

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

当 Hook 返回 "ask" 时,向用户显示的权限提示包含标识 Hook 来源的标签:例如 [User], [Project], [Plugin][Local]。这有助于用户了解哪个配置源正在请求确认。

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

AskUserQuestionExitPlanMode 需要用户交互,通常在非交互模式 (-p 标志) 下会阻塞。返回 permissionDecision: "allow" 并配合 updatedInput 可满足该要求:Hook 从 stdin 读取工具的输入,通过你自己的 UI 收集答案,并在 updatedInput 中返回,以便工具无需提示即可运行。单独返回 "allow" 对这些工具来说不够。对于 AskUserQuestion,回声返回原始 questions 数组,并添加一个将每个问题文本映射到所选答案的 answers 对象。

PreToolUse 以前使用顶级 decisionreason 字段,但这些已在此事件上弃用。改用 hookSpecificOutput.permissionDecisionhookSpecificOutput.permissionDecisionReason。弃用的值 "approve""block" 分别映射到 "allow""deny"。其他事件如 PostToolUse 和 Stop 继续使用顶级 decisionreason 作为其当前格式。

推迟工具调用以供稍后

"defer" 适用于将 claude -p 作为子进程运行并读取其 JSON 输出的集成,例如 Agent SDK 应用程序或基于 Claude Code 构建的自定义 UI。它允许调用进程在工具调用处暂停 Claude,通过自己的界面收集输入,然后从中断处恢复。Claude Code 仅在非交互模式-p 标志下接受此值。在交互式会话中,它会记录警告并忽略 Hook 结果。

defer 值需要 Claude Code v2.1.89 或更高版本。早期版本无法识别它,工具会通过正常权限流程继续。

AskUserQuestion 工具是典型情况:Claude 想问用户一些问题,但没有终端来回答。往返过程如下:

  1. Claude 调用 AskUserQuestionPreToolUse 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, nameinputinput 是 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" 会被忽略并记录警告,工具通过正常权限流程继续。限制的存在是因为恢复只能重新运行一个工具:无法在不留下其他未解决调用的情况下推迟批处理中的一个调用。

如果恢复时推迟的工具不再可用,进程在 Hook 触发前以 stop_reason: "tool_deferred_unavailable"is_error: true 退出。当提供该工具的 MCP 服务器未连接到恢复的会话时发生。deferred_tool_use 负载仍然包含,以便你可以识别哪个工具丢失了。

--resume 恢复工具被推迟时活动的权限模式,因此你无需再次传递 --permission-mode。例外情况是 planbypassPermissions,它们永远不会被继承。在恢复时显式传递 --permission-mode 会覆盖恢复的值。

PermissionRequest

当向用户显示权限弹窗时运行。使用 PermissionRequest 决策控制 代表用户允许或拒绝。

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

PermissionRequest 输入

PermissionRequest Hook 接收 tool_nametool_input 字段(与 PreToolUse Hook 相同),但没有 tool_use_id。可选的 permission_suggestions 数组包含用户通常在权限弹窗中会看到的"始终允许"选项。区别在于 Hook 触发时机:PermissionRequest Hook 在权限弹窗即将向用户显示时运行,而 PreToolUse Hook 在工具执行前(不论权限状态)运行。

{
  "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 Hook 可以允许或拒绝权限请求。除所有 Hook 可用的 JSON 输出字段外,Hook 脚本还可以返回一个包含这些事件特定字段的 decision 对象:

字段 描述
behavior "allow" 授予权限,"deny" 拒绝权限。拒绝和询问规则仍会被评估,因此返回 "allow" 的 Hook 不能覆盖匹配的拒绝规则
updatedInput "allow": 在运行前修改工具的输入参数。替换整个输入对象,因此将未更改的字段与已修改的字段一起包含。修改后的输入会针对拒绝和询问规则重新评估
updatedPermissions "allow": 要应用的权限更新条目数组,例如添加允许规则或更改会话权限模式
message "deny": 告诉 Claude 为什么权限被拒绝
interrupt "deny": 如果为 true,停止 Claude
{
  "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 移除 destination 处给定 behavior 的匹配规则
setMode mode, destination 更改权限模式。有效模式为 default, auto, acceptEdits, dontAsk, bypassPermissionsplan
addDirectories directories, destination 添加工作目录。directories 是路径字符串数组
removeDirectories directories, destination 移除工作目录

setModebypassPermissions 仅在会话已启用绕过模式时生效:--dangerously-skip-permissions, --permission-mode bypassPermissions, --allow-dangerously-skip-permissions, 或设置中的 permissions.defaultMode: "bypassPermissions",且该模式未被 permissions.disableBypassPermissionsMode 禁用。否则更新无效。无论 destination 如何,bypassPermissions 永远不会持久化为 defaultMode

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

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

Hook 可以将接收到的 permission_suggestions 之一作为自己的 updatedPermissions 输出回显,这等效于用户在弹窗中选择那个"始终允许"选项。

PostToolUse

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

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

PostToolUse 输入

PostToolUse Hook 在工具已成功执行后触发。输入同时包括 tool_input(发送给工具的参数)和 tool_response(返回的结果)。两者的确切 schema 取决于工具。

{
  "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",
    "success": true
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
字段 描述
duration_ms 可选。工具执行时间(毫秒)。排除权限提示和 PreToolUse Hook 中花费的时间

PostToolUse 决策控制

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

字段 描述
decision "block" 在工具结果旁边添加 reason。Claude 仍然看到原始输出;要替换它,使用 updatedToolOutput
reason decision"block" 时向 Claude 显示的解释
additionalContext 与工具结果一起添加到 Claude 上下文的字符串。见为 Claude 添加上下文
updatedToolOutput 在发送给 Claude 之前用提供的值替换工具的输出。值必须匹配工具的输出形状
updatedMCPToolOutput 仅替换 MCP 工具 的输出。优先使用适用于所有工具的 updatedToolOutput

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

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

updatedToolOutput 只改变 Claude 看到的内容。Hook 触发时工具已运行,因此任何写入的文件、执行的命令或发送的网络请求都已生效。遥测,如 OpenTelemetry 工具 span 和分析事件,也在 Hook 运行前捕获原始输出。要在运行前阻止或修改工具调用,改用 PreToolUse Hook。

替换值必须匹配工具的输出形状。内置工具返回结构化对象而非纯字符串。例如,Bash 返回包含 stdout, stderr, interruptedisImage 字段的对象。对于内置工具,不匹配工具输出 schema 的值会被忽略,使用原始输出。MCP 工具输出直接传递,无 schema 验证。删除 Claude 需要的错误细节可能会导致它在错误假设下继续。

PostToolUseFailure

当工具执行失败时运行。此事件针对抛出错误或返回失败结果的工具调用触发。用于记录失败、发送警报或向 Claude 提供纠正性反馈。

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

PostToolUseFailure 输入

PostToolUseFailure Hook 接收与 PostToolUse 相同的 tool_nametool_input 字段,以及作为顶级字段的错误信息:

{
  "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": "Command exited with non-zero status code 1",
  "is_interrupt": false,
  "duration_ms": 4187
}
字段 描述
error 描述出错原因的字符串
is_interrupt 可选布尔值,指示失败是否由用户中断引起
duration_ms 可选。工具执行时间(毫秒)。排除权限提示和 PreToolUse Hook 中花费的时间

PostToolUseFailure 决策控制

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

字段 描述
additionalContext 与错误一起添加到 Claude 上下文的字符串。见为 Claude 添加上下文
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PostToolBatch

在批处理中的每个工具调用都已解析后、Claude Code 向下一个模型发送请求之前运行一次。PostToolUse 每个工具触发一次,这意味着当 Claude 进行并行工具调用时它会并发触发。PostToolBatch 恰好触发一次,包含完整的批次,因此是注入依赖于运行工具集合(而非单个工具)的上下文的正确位置。此事件没有 matcher。

PostToolBatch 输入

通用输入字段外,PostToolBatch Hook 还接收描述批处理中每个工具调用的 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,这意味着行号前缀文本而非原始文件内容。响应可能很大,因此只解析你需要的字段。

tool_response 形状与 PostToolUse 不同。PostToolUse 传递工具的结构化 Output 对象,例如 Write{filePath: "...", success: true}PostToolBatch 传递模型看到的序列化 tool_result 内容。

PostToolBatch 决策控制

PostToolBatch Hook 可以为 Claude 注入上下文。除所有 Hook 可用的 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 会在下一次模型调用前停止代理循环。

PermissionDenied

自动模式分类器拒绝工具调用时运行。此 Hook 仅在自动模式下触发:它不会在你手动拒绝权限弹窗、PreToolUse Hook 阻断调用或匹配 deny 规则时运行。用于记录分类器拒绝、调整配置或告诉模型可以重试工具调用。

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

PermissionDenied 输入

通用输入字段外,PermissionDenied Hook 还接收 tool_name, tool_input, tool_use_idreason

{
  "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": "Auto mode denied: command targets a path outside the project"
}
字段 描述
reason 分类器对工具调用被拒绝原因的解释

PermissionDenied 决策控制

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

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

retrytrue 时,Claude Code 向对话添加一条消息,告诉模型可以重试工具调用。拒绝本身不会被撤销。如果 Hook 不返回 JSON,或返回 retry: false,则拒绝成立,模型收到原始的拒绝消息。

Notification

当 Claude Code 发送通知时运行。匹配通知类型:permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_complete, elicitation_response。省略 matcher 可为所有通知类型运行 Hook。

使用单独的 matcher 根据通知类型运行不同的处理器。此配置在 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 Hook 还接收通知文本的 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 Hook 不能阻断或修改通知。它们旨在用于副作用,例如将通知转发到外部服务。通用 JSON 输出字段(如 systemMessage)适用。

SubagentStart

当通过 Agent 工具启动 Claude Code 子代理时运行。支持按代理类型名称过滤的 matcher。对于内置代理,这是代理名称,如 general-purpose, ExplorePlan。对于自定义子代理,这是代理 frontmatter 中的 name 字段,而非文件名。

SubagentStart 输入

通用输入字段外,SubagentStart Hook 还接收子代理唯一标识符的 agent_id 和代理名称的 agent_type(内置代理如 "general-purpose", "Explore", "Plan",或自定义代理名称)。

{
  "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 Hook 不能阻断子代理创建,但可以向子代理注入上下文。除所有 Hook 可用的 JSON 输出字段外,还可以返回:

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

SubagentStop

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

SubagentStop 输入

通用输入字段外,SubagentStop Hook 还接收 stop_hook_active, agent_id, agent_type, agent_transcript_pathlast_assistant_messageagent_type 字段是用于 matcher 过滤的值。transcript_path 是主会话的转录,而 agent_transcript_path 是子代理自身的转录,存储在嵌套的 subagents/ 文件夹中。last_assistant_message 字段包含子代理最终响应的文本内容,因此 Hook 无需解析转录文件即可访问它。

SubagentStop Hook 还接收在 Stop 输入 中描述的 background_taskssession_crons 数组,自 Claude Code v2.1.145 起可用。两个数组都限定在父会话而非子代理范围内。

{
  "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 Hook 使用与 Stop Hook 相同的决策控制格式。它们不支持 additionalContext。返回 decision: "block" 并附带 reason 会使子代理继续运行,并将 reason 作为其下一条指令传递给子代理。若要在子代理返回后向父会话注入上下文,改用 PostToolUse Hook 匹配 Agent 工具。

TaskCreated

当通过 TaskCreate 工具创建任务时运行。用于强制执行命名约定、要求任务描述,或阻止创建某些任务。

TaskCreated Hook 以代码 2 退出时,任务不会被创建,并且 stderr 消息作为反馈反馈给模型。要完全停止队友而非重新运行它,返回 JSON {"continue": false, "stopReason": "..."}。TaskCreated Hook 不支持 matcher,每次事件触发时都会运行。

TaskCreated 输入

通用输入字段外,TaskCreated Hook 还接收 task_id, task_subject 以及可选的 task_description, teammate_nameteam_name

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "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": "my-project"
}
字段 描述
task_id 正在创建的任务的标识符
task_subject 任务的标题
task_description 任务的详细描述。可能缺失
teammate_name 创建任务的队友的名称。可能缺失
team_name 团队的名称。可能缺失

TaskCreated 决策控制

TaskCreated Hook 支持两种控制任务创建的方式:

  • 退出码 2: 任务不会被创建,并且 stderr 消息作为反馈反馈给模型。
  • JSON {"continue": false, "stopReason": "..."}: 完全停止队友,匹配 Stop Hook 行为。stopReason 显示给用户。

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

#!/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

当任务被标记为完成时运行。这在两种情况下触发:当任何代理通过 TaskUpdate 工具显式标记任务为完成时,或者当代理团队队友在存在进行中任务的情况下完成其轮次时。用于在任务关闭前强制执行完成标准,如通过测试或 lint 检查。

TaskCompleted Hook 以代码 2 退出时,任务不会被标记为完成,并且 stderr 消息作为反馈反馈给模型。要完全停止队友而非重新运行它,返回 JSON {"continue": false, "stopReason": "..."}。TaskCompleted Hook 不支持 matcher,每次事件触发时都会运行。

TaskCompleted 输入

通用输入字段外,TaskCompleted Hook 还接收 task_id, task_subject 以及可选的 task_description, teammate_nameteam_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": "my-project"
}
字段 描述
task_id 正在完成的任务的标识符
task_subject 任务的标题
task_description 任务的详细描述。可能缺失
teammate_name 完成任务队友的名称。可能缺失
team_name 团队的名称。可能缺失

TaskCompleted 决策控制

TaskCompleted Hook 支持两种控制任务完成的方式:

  • 退出码 2: 任务不会被标记为完成,并且 stderr 消息作为反馈反馈给模型。
  • JSON {"continue": false, "stopReason": "..."}: 完全停止队友,匹配 Stop Hook 行为。stopReason 显示给用户。

此示例运行测试,如果失败则阻止任务完成:

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

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 代理完成响应时运行。如果停止是由用户中断引起的,则不运行。API 错误改为触发 StopFailure

/goal 命令是会话范围基于提示的 Stop Hook 的内置快捷方式。当你希望 Claude 继续工作直到某个条件满足,而又不想编写 Hook 配置时,请使用它。

Stop 输入

通用输入字段外,Stop Hook 还接收 stop_hook_active, last_assistant_message, background_taskssession_cronsstop_hook_active 字段在 Claude Code 已因 Stop Hook 结果而继续时是 true。检查此值或处理转录以避免在永不会解决的条件下阻塞。Claude Code 在连续 8 次阻塞后覆盖 Hook 并结束此轮。

last_assistant_message 字段包含 Claude 最终响应的文本内容,因此 Hook 无需解析转录文件即可访问它。

background_taskssession_crons 数组(自 Claude Code v2.1.145 起可用)让 Hook 区分"会话完成"和"会话暂停等待后台工作唤醒"。当任务注册表可访问时两个数组都存在,当没有事务在进行或计划时它们为空。

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 服务器名称。仅对 monitorMCP task 任务存在
tool MCP 工具名称。仅对 monitorMCP task 任务存在
name 工作流名称。仅对 workflow 任务存在

session_crons 中的每个条目描述一个会话范围的定时唤醒,来自 CronCreate, ScheduleWakeup/loop

字段 描述
id Cron 任务标识符
schedule Cron 表达式,例如 0 9 * * 1-5
recurring 一次唤醒为 false(其 schedule 编码单次触发时间),重复任务为 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 决策控制

StopSubagentStop Hook 可以控制 Claude 是否继续。除所有 Hook 可用的 JSON 输出字段外,Hook 脚本还可以返回这些事件特定字段:

字段 描述
decision "block" 阻止 Claude 停止。省略允许 Claude 停止
reason decision"block" 时必须提供。告诉 Claude 为什么它应该继续
{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}

StopFailure

当轮次因 API 错误结束时代替 Stop 运行。输出和退出码被忽略。用于在 Claude 因速率限制、认证问题或其他 API 错误无法完成响应时记录失败、发送警报或采取恢复措施。

StopFailure 输入

通用输入字段外,StopFailure Hook 还接收 error、可选的 error_details 和可选的 last_assistant_messageerror 字段标识错误类型,并用于 matcher 过滤。

字段 描述
error 错误类型:rate_limit, authentication_failed, oauth_org_not_allowed, billing_error, invalid_request, model_not_found, server_error, max_output_tokensunknown
error_details 有关错误的更多详情(可用时)
last_assistant_message 对话中渲染的错误文本。与 StopSubagentStop 不同(这些字段保存 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 Hook 没有决策控制。它们仅用于通知和日志记录目的。

TeammateIdle

代理团队队友完成其轮次后即将进入空闲状态时运行。用于在队友停止工作前强制执行质量门禁,例如要求通过 lint 检查或验证输出文件存在。

TeammateIdle Hook 以代码 2 退出时,队友会收到 stderr 消息作为反馈并继续工作而不是进入空闲状态。要完全停止队友而非重新运行它,返回 JSON {"continue": false, "stopReason": "..."}。TeammateIdle Hook 不支持 matcher,每次事件触发时都会运行。

TeammateIdle 输入

通用输入字段外,TeammateIdle Hook 还接收 teammate_nameteam_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": "my-project"
}
字段 描述
teammate_name 即将进入空闲状态的队友的名称
team_name 团队的名称

TeammateIdle 决策控制

TeammateIdle Hook 支持两种控制队友行为的方式:

  • 退出码 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

当会话期间配置文件发生变化时运行。用于审计设置更改、执行安全策略或阻止对配置文件的未授权修改。

ConfigChange Hook 会针对设置文件、托管策略设置和技能文件的更改触发。输入中的 source 字段告诉你哪种类型的配置发生了更改,可选的 file_path 字段提供更改文件的路径。

matcher 过滤配置源:

Matcher 触发时机
user_settings ~/.claude/settings.json 更改
project_settings .claude/settings.json 更改
local_settings .claude/settings.local.json 更改
policy_settings 托管策略设置更改
skills .claude/skills/ 中的技能文件更改

此示例出于安全审计目的记录所有配置更改:

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

ConfigChange 输入

通用输入字段外,ConfigChange Hook 还接收 source 和可选的 file_pathsource 字段指示哪种配置类型发生了更改,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 Hook 可以阻止配置更改生效。使用退出码 2 或 JSON decision 来防止更改。阻止时,新设置不会应用于正在运行的会话。

字段 描述
decision "block" 阻止配置更改被应用。省略允许更改
reason decision"block" 时向用户显示的解释
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

policy_settings 更改无法被阻止。Hook 仍然会针对 policy_settings 源触发,因此你可以将它们用于审计日志,但任何阻止决策都会被忽略。这确保了企业管理的设置始终生效。

CwdChanged

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

CwdChanged Hook 可以访问 CLAUDE_ENV_FILE。写入该文件的变量会像 SessionStart Hook 一样持续到会话的后续 Bash 命令中。

CwdChanged 不支持 matcher,每次目录更改时都会触发。

CwdChanged 输入

通用输入字段外,CwdChanged Hook 还接收 old_cwdnew_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 输出

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

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

CwdChanged Hook 没有决策控制。它们不能阻止目录更改。

FileChanged

当被监视的文件在磁盘上发生变化时运行。用于在项目配置文件被修改时重新加载环境变量。

此事件的 matcher 有两个作用:

  • 构建监视列表: 该值按 | 分割,每个段在工作目录中注册为字面量文件名,因此 ".envrc|.env" 恰好监视这两个文件。正则表达式在此处无用:像 ^\.env 这样的值会监视一个字面名为 ^\.env 的文件。
  • 过滤哪些 Hook 运行: 当被监视的文件更改时,相同的值使用标准 matcher 规则 针对更改文件的基名来过滤应运行哪些 Hook 组。

FileChanged Hook 可以访问 CLAUDE_ENV_FILE。写入该文件的变量会像 SessionStart Hook 一样持续到会话的后续 Bash 命令中。

FileChanged 输入

通用输入字段外,FileChanged Hook 还接收 file_pathevent

字段 描述
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 输出

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

字段 描述
watchPaths 绝对路径数组。替换当前动态监视列表(来自你的 matcher 配置的路径始终被监视)。当你的 Hook 脚本发现基于更改文件的额外文件需要监视时使用此字段

FileChanged Hook 没有决策控制。它们不能阻止文件更改发生。

WorktreeCreate

当你运行 claude --worktree子代理使用 isolation: "worktree" 时,Claude Code 会使用 git worktree 创建一个隔离的工作副本。如果你配置了 WorktreeCreate Hook,它将替换默认的 git 行为,让你可以使用不同的版本控制系统,如 SVN、Perforce 或 Mercurial。

由于 Hook 完全替换了默认行为,.worktreeinclude 不会被处理。如果你需要将 .env 等本地配置文件复制到新的 worktree 中,请在 Hook 脚本中执行此操作。

Hook 必须返回所创建 worktree 目录的绝对路径。Claude Code 使用此路径作为隔离会话的工作目录。Command Hook 在 stdout 上打印它;HTTP Hook 通过 hookSpecificOutput.worktreePath 返回它。

此示例创建一个 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 Hook 还接收 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 Hook 不使用标准的 allow/block 决策模型。而是由 Hook 的成功或失败决定结果。Hook 必须返回所创建 worktree 目录的绝对路径:

  • Command Hook (type: "command"): 在 stdout 上打印路径。
  • HTTP Hook (type: "http"): 在响应体中返回 { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }

如果 Hook 失败或未产生路径,worktree 创建失败并报错。

WorktreeRemove

WorktreeCreate 的清理对应项。此 Hook 在移除 worktree 时触发,无论是退出 --worktree 会话并选择移除它,还是具有 isolation: "worktree" 的子代理完成时。对于基于 git 的 worktree,Claude 使用 git worktree remove 自动处理清理。如果你为非 git 版本控制系统配置了 WorktreeCreate Hook,请与 WorktreeRemove Hook 配对以处理清理。如果没有,worktree 目录会留在磁盘上。

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

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

WorktreeRemove 输入

通用输入字段外,WorktreeRemove Hook 还接收 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 没有决策控制。它们不能阻止 worktree 移除,但可以执行清理任务,如移除版本控制状态或存档更改。Hook 失败仅在调试模式下记录。

PreCompact

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

matcher 值指示压缩是手动还是自动触发的:

Matcher 触发时机
manual /compact
auto 上下文窗口满时自动压缩

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

阻止自动压缩在不同时间触发时有不同效果。如果压缩是在上下文限制之前主动触发的,Claude Code 会跳过它,对话继续未压缩。如果压缩是为了从 API 已返回的上下文限制错误中恢复,则底层错误暴露出来,当前请求失败。

PreCompact 输入

通用输入字段外,PreCompact Hook 还接收 triggercustom_instructions。对于 manualcustom_instructions 包含用户传递给 /compact 的内容。对于 autocustom_instructions 为空。

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

PostCompact

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

PreCompact 相同的 matcher 值适用:

Matcher 触发时机
manual /compact 之后
auto 上下文窗口满时自动压缩之后

PostCompact 输入

通用输入字段外,PostCompact Hook 还接收 triggercompact_summarycompact_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 Hook 没有决策控制。它们不能影响压缩结果,但可以执行后续任务。

SessionEnd

当 Claude Code 会话结束时运行。用于清理任务、记录会话统计数据或保存会话状态。支持按退出原因过滤的 matcher。

Hook 输入中的 reason 字段指示会话结束的原因:

Reason 描述
clear 使用 /clear 命令清除会话
resume 使用交互式 /resume 切换会话
logout 用户已登出
prompt_input_exit 用户在提示输入可见时退出
bypass_permissions_disabled 绕过权限模式被禁用
other 其他退出原因

SessionEnd 输入

通用输入字段外,SessionEnd Hook 还接收指示会话结束原因的 reason 字段。所有值见上面的 reason 表

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

SessionEnd Hook 没有决策控制。它们不能阻止会话终止,但可以执行清理任务。

SessionEnd Hook 的默认超时为 1.5 秒。这适用于会话退出、/clear 以及通过交互式 /resume 切换会话。如果 Hook 需要更多时间,请在 Hook 配置中设置每个 Hook 的 timeout。总预算会自动提高到设置文件中配置的最高每个 Hook 超时,最高 60 秒。插件提供的 Hook 上设置的超时不会提高预算。要显式覆盖预算,以毫秒为单位设置 CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 环境变量。

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

Elicitation

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

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

Elicitation 输入

通用输入字段外,Elicitation Hook 还接收 mcp_server_name, message 以及可选的 mode, url, elicitation_idrequested_schema 字段。

对于表单模式 elicitation(最常见的情况):

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "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 模式 elicitation(基于浏览器的认证):

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "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 对象 要提交的表单字段值。仅当 actionaccept 时使用

退出码 2 会拒绝 elicitation 并向用户显示 stderr。

ElicitationResult

在用户响应 MCP elicitation 后运行。Hook 可以在响应发送回 MCP 服务器之前观察、修改或阻止它。

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

ElicitationResult 输入

通用输入字段外,ElicitationResult Hook 还接收 mcp_server_name, action 以及可选的 mode, elicitation_idcontent 字段。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "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 对象 覆盖表单字段值。仅当 actionaccept 时才有意义

退出码 2 会阻止响应,将有效操作更改为 decline

Prompt-based Hooks

除 Command、HTTP 和 MCP Tool Hook 外,Claude Code 还支持使用 LLM 来评估是否允许或阻止某个操作的 Prompt-based Hook(type: "prompt"),以及启动具有工具访问权限的 Agents (type: "agent")。并非所有事件都支持每种 Hook 类型。

支持所有五种 Hook 类型(command, http, mcp_tool, promptagent)的事件:

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

支持 command, httpmcp_tool Hook 但不支持 promptagent 的事件:

  • ConfigChange
  • CwdChanged
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • Notification
  • PostCompact
  • PreCompact
  • SessionEnd
  • StopFailure
  • SubagentStart
  • WorktreeCreate
  • WorktreeRemove

SessionStartSetup 支持 commandmcp_tool Hook。它们不支持 http, promptagent Hook。

Prompt-based Hooks 的工作原理

Prompt-based Hook 不是执行 Bash 命令,而是:

  1. 将 Hook 输入和你的提示发送给 Claude 模型(默认 Haiku)
  2. LLM 返回包含决策的结构化 JSON
  3. Claude Code 自动处理该决策

Prompt Hook 配置

type 设置为 "prompt" 并提供 prompt 字符串代替 command。使用 $ARGUMENTS 占位符将 Hook 的 JSON 输入数据注入到你的提示文本中。Claude Code 将组合的提示和输入发送给快速 Claude 模型,模型返回 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 当提示返回 ok: false 时,将原因反馈给 Claude 并继续该轮而不是停止。默认:false。实现为结果 decision: "block" 上的 continue: true。有关每个事件的行为,请参见响应 schema

响应 Schema

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

{
  "ok": true | false,
  "reason": "Explanation for the decision"
}
字段 描述
ok true 允许。false 产生 decision: "block"。每个事件的行为见下文
reason okfalse 时必须提供。用作阻止原因

ok: false 时发生什么取决于事件:

  • StopSubagentStop: 原因作为下一条指令反馈给 Claude,轮次继续
  • PreToolUse: 工具调用被拒绝,原因作为工具错误返回给 Claude,等效于 command Hook 的 permissionDecision: "deny"
  • PostToolUse: 默认情况下轮次结束,原因作为警告行出现在聊天中。设置 continueOnBlock: true 将原因反馈给 Claude 并继续该轮次
  • PostToolBatch, UserPromptSubmitUserPromptExpansion: 轮次结束,原因显示为警告行。无论 continue 如何,这些事件在 decision: "block" 时都会结束该轮
  • PostToolUseFailure, TaskCreatedTaskCompleted: 原因作为工具错误返回给 Claude,类似于 PreToolUse
  • TeammateIdle: 默认情况下队友停止,原因显示为警告行。设置 continueOnBlock: true 将原因反馈给队友并让其继续工作
  • PermissionRequest: ok: false 无效。要从 Hook 拒绝批准,使用返回 hookSpecificOutput.decision.behavior: "deny"command Hook
  • PermissionDenied: ok: false 无效,因为拒绝已经发生。此事件读取的唯一输出是 hookSpecificOutput.retry,而 prompt 和 agent Hook 无法设置它——它们在此事件上运行,但输出被丢弃。使用 command Hook 返回 retry

如果需要在任何事件上进行更精细的控制,使用 command Hook 配合决策控制中描述的每个事件字段。

示例:多条件 Stop Hook

Stop Hook 使用详细的提示在允许 Claude 停止之前检查三个条件。如果 "ok"false,Claude 使用提供的原因作为其下一条指令继续工作。SubagentStop Hook 使用相同的格式来评估子代理是否应该停止:

{
  "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
          }
        ]
      }
    ]
  }
}

Agent-based Hooks

Agent Hook 是实验性功能。行为和配置可能在将来版本中发生变化。对于生产工作流,优先使用 command Hook

Agent-based Hook (type: "agent") 类似于 prompt-based Hook,但具有多轮工具访问权限。它不是一个单次 LLM 调用,而是一个可以读取文件、搜索代码和检查代码库以验证条件的子代理。Agent Hook 支持与 prompt-based Hook 相同的事件。

Agent Hook 的工作原理

当 Agent Hook 触发时:

  1. Claude Code 使用你的提示和 Hook 的 JSON 输入启动一个子代理
  2. 子代理可以使用 Read、Grep 和 Glob 等工具进行调查
  3. 最多 50 轮后,子代理返回结构化 { "ok": true/false } 决策
  4. Claude Code 以与 prompt Hook 相同的方式处理决策

当验证需要检查实际文件或测试输出,而不仅仅是评估 Hook 输入数据时,Agent Hook 非常有用。

Agent Hook 配置

type 设置为 "agent" 并提供 prompt 字符串。配置字段与 prompt Hook 相同,默认超时更长:

字段 必需 描述
type 必须为 "agent"
prompt 描述要验证的内容的提示。使用 $ARGUMENTS 作为 Hook 输入 JSON 的占位符
model 要使用的模型。默认为快速模型
timeout 超时秒数。默认:60

响应 schema 与 prompt Hook 相同:{ "ok": true } 允许,{ "ok": false, "reason": "..." } 阻止。

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
          }
        ]
      }
    ]
  }
}

在后台运行 Hook

默认情况下,Hook 会阻塞 Claude 的执行直到它们完成。对于长期运行的任务,如部署、测试套件或外部 API 调用,设置 "async": true 以在后台运行 Hook,同时 Claude 继续工作。异步 Hook 不能阻止或控制 Claude 的行为:响应字段如 decisionpermissionDecisioncontinue 无效,因为它们本来要控制的操作已经完成。

配置异步 Hook

向 command Hook 配置添加 "async": true,以在后台运行而不阻塞 Claude。此字段仅在 type: "command" Hook 上可用。

此 Hook 在每次 Write 工具调用后运行测试脚本。Claude 立即继续工作,而 run-tests.sh 最多执行 120 秒。脚本完成时,其输出在下一轮对话中传递:

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

timeout 字段设置后台进程的最大秒数。如果未指定,异步 Hook 使用与同步 Hook 相同的 10 分钟默认值。

异步 Hook 如何执行

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

后台进程退出后,如果 Hook 生成了带有 additionalContext 字段的 JSON 响应,则内容作为上下文在下一轮对话中传递给 Claude。systemMessage 字段显示给你,而不是 Claude。

异步 Hook 完成通知默认被抑制。要查看它们,使用 Ctrl+O 启用详细模式或使用 --verbose 启动 Claude Code。

示例:文件更改后运行测试

此 Hook 在 Claude 写入文件时在后端启动测试套件,然后在测试完成时将结果报告给 Claude。将此脚本保存到项目的 .claude/hooks/run-tests-async.sh 并使用 chmod +x 使其可执行:

#!/bin/bash

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

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.jsonasync: true 标志让 Claude 在测试运行时继续工作:

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

限制

与同步 Hook 相比,异步 Hook 有几个约束:

  • 只有 type: "command" Hook 支持 async。Prompt-based Hook 不能异步运行。
  • 异步 Hook 不能阻止工具调用或返回决策。Hook 完成时,触发它的动作已经进行。
  • Hook 输出在下一轮对话中传递。如果会话空闲,响应会等待直到下一次用户交互。例外:以代码 2 退出的 asyncRewake Hook 即使会话空闲也会立即唤醒 Claude。
  • 每次执行都会创建一个单独的后台进程。同一异步 Hook 的多次触发没有去重。

安全考虑

免责声明

Command Hook 以你的系统用户的全权限运行。

Command Hook 以你的用户全权限执行 shell 命令。它们可以修改、删除或访问你的用户账户可以访问的任何文件。在将任何 Hook 命令添加到配置之前,请审查并测试它们。

安全最佳实践

编写 Hook 时请牢记这些做法:

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

Windows PowerShell 工具

在 Windows 上,可以通过在 command Hook 上设置 "shell": "powershell" 在 PowerShell 中运行单个 Hook。Hook 直接启动 PowerShell,因此无论是否设置 CLAUDE_CODE_USE_POWERSHELL_TOOL 都有效。Claude Code 会自动检测 pwsh.exe(PowerShell 7+),回退到 powershell.exe(5.1)。

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

调试 Hook

Hook 执行详情,包括哪些 Hook 匹配、它们的退出码以及完整的 stdout 和 stderr,都会写入调试日志文件。使用 claude --debug-file <path> 启动 Claude Code 以将日志写入已知位置,或运行 claude --debug 并在 ~/.claude/debug/<session-id>.txt 中阅读日志。--debug 标志不会打印到终端。

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 600000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

要获得更细粒度的 Hook 匹配详情,设置 CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose 可看到额外的日志行,如 Hook matcher 计数和查询匹配。

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

常见问题

Hook 事件不触发怎么办?

运行 /hooks 确认 Hook 出现在正确的事件下。检查 matcher 是否与工具名称完全匹配(区分大小写)。PermissionRequest 在非交互模式(-p)下不触发,改用 PreToolUse。对于 UserPromptSubmit, Stop 等不支持 matcher 的事件,它们总是触发,检查是否有语法错误导致设置文件未加载。

退出码 2 后操作仍然继续进行?

确认退出码确实为 2。stderr 文本会反馈给 Claude,因此检查错误消息是否出现。PostToolUsePostToolUseFailure 不能通过退出码阻断(工具已运行)。PermissionDenied 的退出码 2 被忽略,拒绝已经发生;要允许重试,在 JSON 输出中返回 hookSpecificOutput.retry: trueStopFailure 和其他类似事件忽略所有退出码;它们用于通知而非控制。

Prompt Hook 返回 ok: false 后没有效果?

检查事件类型。PermissionRequestPermissionDenied 忽略 prompt Hook 的 ok: false。对于 PermissionRequest,要拒绝批准,使用 command Hook 返回 hookSpecificOutput.decision.behavior: "deny"PermissionDenied 唯一读取的输出是 hookSpecificOutput.retry,而 prompt/agent Hook 无法设置它,请使用 command Hook。其他事件(如 Stop, PreToolUse, PostToolUse)在 ok: false 时行为各不相同,详见响应 Schema