Claude Code 自动化与排错
Claude Code 自动化与排错
使用 Hooks 实现自动化
11 分钟阅读
用钩子自动化操作
在 Claude Code 编辑文件、完成任务或需要输入时自动运行 shell 命令。格式化代码、发送通知、校验命令,并强制执行项目规则。
钩子是在 Claude Code 生命周期特定时刻执行的用户自定义 shell 命令。它们对 Claude Code 的行为提供确定性的控制,确保某些操作始终发生,而不是依赖大模型自行选择是否运行它们。可以用钩子强制执行项目规则、自动化重复性任务,并将 Claude Code 与你现有的工具集成。
对于需要判断力、而非确定性规则的决策,你也可以使用基于提示词的钩子或基于智能体的钩子,用一个 Claude 模型来评估条件。
关于扩展 Claude Code 的其他方式,请参阅技能(为 Claude 提供额外的指令和可执行命令)、子智能体(在隔离的上下文中运行任务),以及插件(打包扩展以跨项目共享)。
搭建你的第一个钩子
要创建一个钩子,在设置文件中添加一个 hooks 区块。本教程会创建一个桌面通知钩子,这样每当 Claude 在等待你的输入时,你都会收到提醒,而不必一直盯着终端。
将钩子添加到你的设置中
打开 ~/.claude/settings.json,添加一个 Notification 钩子。以下示例使用 macOS 上的 osascript;关于 Linux 和 Windows 的命令,请参阅当 Claude 需要输入时获得通知。
如果你的设置文件已经有一个 hooks 键,请把 Notification 添加为现有事件键的同级项,而不是替换整个对象。每个事件名称都是同一个 hooks 对象中的一个键:
你也可以在 CLI 中描述你想要的内容,让 Claude 帮你写这个钩子。
验证配置
输入 /hooks 打开钩子浏览器。你会看到所有可用钩子事件的列表,每个已配置钩子的事件旁边都会显示一个数量。选择 Notification,确认你的新钩子出现在列表中。选中该钩子会显示其详情:事件、匹配器、类型、源文件和命令。
测试该钩子
按 Esc 返回 CLI。让 Claude 做一件需要权限的事,然后切换离开终端。你应该会收到一条桌面通知。
你可以自动化什么
钩子让你能在 Claude Code 生命周期的关键节点运行代码:编辑后格式化文件、在命令执行前阻止它、在 Claude 需要输入时发送通知、在会话启动时注入上下文,等等。关于钩子事件的完整列表,请参阅钩子参考文档。
每个示例都包含一个可直接使用的配置块,你可以把它添加到设置文件中。
关于运行一次独立模型审查、并将发现结果反馈回会话的钩子的生产环境示例,请参阅security-guidance 插件如何与 Claude Code 集成。
当 Claude 需要输入时获得通知
每当 Claude 完成工作、需要你输入时,获得一条桌面通知,这样你就可以切换去做其他任务,而不必查看终端。
这个钩子使用 Notification 事件,它会在 Claude 等待输入或权限时触发。下面的每个标签页都使用了对应平台的原生通知命令。把它添加到 ~/.claude/settings.json:
- macOS
- Linux
- Windows(PowerShell)
如果没有出现通知
如果没有出现通知
osascript 通过内置的 Script Editor 应用来发送通知。如果 Script Editor 没有通知权限,该命令会静默失败,macOS 也不会提示你授予权限。在终端中运行一次以下命令,让 Script Editor 出现在你的通知设置中:
此时还不会出现任何内容。打开System Settings > Notifications,在列表中找到Script Editor,打开Allow Notifications。再次运行该命令,确认测试通知出现。
空的 matcher 会对所有通知类型触发。要只对特定事件触发,将其设为以下值之一:
| 匹配器 | 触发时机 |
|---|---|
permission_prompt | Claude 需要你批准一次工具使用 |
idle_prompt | Claude 已完成,正在等待你的下一个提示词 |
auth_success | 身份验证完成 |
elicitation_dialog | 某个 MCP 服务器打开了一个征询表单 |
elicitation_complete | 某个 MCP 征询表单被提交或关闭 |
elicitation_response | 某个 MCP 征询回复被发送回该服务器 |
agent_needs_input | 某个后台会话开始等待你的输入。只在智能体视图打开时触发 |
agent_completed | 某个后台会话完成或失败。只在智能体视图打开时触发 |
agent_needs_input 和 agent_completed 匹配器需要 Claude Code v2.1.198 或更高版本。
输入 /hooks 并选择 Notification,确认该钩子已注册。关于完整的事件模式,请参阅Notification 参考。
编辑后自动格式化代码
在 Claude 编辑的每个文件上自动运行Prettier,让格式保持一致,无需手动干预。
这个钩子使用带 Edit|Write 匹配器的 PostToolUse 事件,因此只在文件编辑工具之后运行。该命令用 jq 提取被编辑的文件路径,并将其传给 Prettier。把它添加到你项目根目录的 .claude/settings.json 中:
在 Claude Code v2.1.191 或更高版本上,你也可以把匹配器写成 Edit,Write,因为在这些版本上,| 和 , 对工具名称匹配器而言是可互换的列表分隔符。
本页的 Bash 示例使用 jq 进行 JSON 解析。在 macOS 上用 brew install jq 安装,在 Debian 和 Ubuntu 上用 apt-get install jq 安装,或参阅jq 下载页面。
阻止编辑受保护的文件
阻止 Claude 修改敏感文件,例如 .env、package-lock.json,或 .git/ 中的任何内容。Claude 会收到解释为何该次编辑被阻止的反馈,从而可以调整其方案。
以下示例使用一个由该钩子调用的独立脚本文件。该脚本会将目标文件路径与一份受保护模式列表进行比对,并以退出码 2 退出以阻止该次编辑。
创建钩子脚本
将以下内容保存到 .claude/hooks/protect-files.sh:
在 macOS 和 Linux 上将该脚本设为可执行
钩子脚本必须是可执行的,才能被 Claude Code 运行:
注册该钩子
在 .claude/settings.json 中添加一个 PreToolUse 钩子,在任何 Edit 或 Write 工具调用之前运行该脚本:
压缩后重新注入上下文
当 Claude 的上下文窗口填满时,压缩会对对话进行摘要以释放空间。这可能会丢失重要细节。使用带 compact 匹配器的 SessionStart 钩子,在每次压缩后重新注入关键上下文。
你的命令写入 stdout 的任何文本都会被加入 Claude 的上下文。以下示例提醒 Claude 项目约定和近期的工作。把它添加到你项目根目录的 .claude/settings.json 中:
你可以把这里的 echo 换成任何能产出动态输出的命令,例如 git log --oneline -5 来展示最近的提交。要在每次会话启动时都注入上下文,可以考虑改用CLAUDE.md。关于环境变量,请参阅参考文档中的 CLAUDE_ENV_FILE。
审计配置更改
追踪会话期间设置或技能文件何时发生变化。ConfigChange 事件会在外部进程或编辑器修改某个配置文件时触发,因此你可以为合规目的记录更改,或阻止未经授权的修改。
以下示例将每次更改追加到一份审计日志中。把它添加到 ~/.claude/settings.json:
该匹配器按配置类型筛选:user_settings、project_settings、local_settings、policy_settings 或 skills。要阻止某次更改生效,以退出码 2 退出,或返回 {"decision": "block"}。完整的输入模式请参阅ConfigChange 参考文档。
目录或文件变化时重新加载环境
有些项目会根据你所在的目录设置不同的环境变量。像 direnv 这样的工具会在你的 shell 中自动完成这一点,但 Claude 的 Bash 工具不会自行获取这些变化。
将 SessionStart 钩子与 CwdChanged 钩子配合使用可以解决这个问题。SessionStart 会加载你启动所在目录的变量,CwdChanged 则在 Claude 每次切换目录时重新加载它们。两者都会写入 CLAUDE_ENV_FILE,Claude Code 会在每条 Bash 命令之前将其作为脚本前导运行。把它添加到 ~/.claude/settings.json:
在每个带有 .envrc 的目录中运行一次 direnv allow,以允许 direnv 加载它。如果你使用 devbox 或 nix 而不是 direnv,同样的模式也适用,只需把 direnv export bash 换成 devbox shellenv 或 devbox global shellenv。
要响应特定文件的变化,而不是每次目录变化,可以使用 FileChanged,并用一个以 | 分隔要监视的文件名的 matcher。在构建监视列表时,Claude Code 会把这个值拆分成字面文件名,而不是把它当作正则表达式来求值。关于这个值如何同时筛选文件变化时运行哪些钩子分组,请参阅FileChanged。以下示例监视工作目录中的 .envrc 和 .env:
关于输入模式、watchPaths 输出,以及 CLAUDE_ENV_FILE 的详情,请参阅CwdChanged 和 FileChanged 参考条目。
自动批准特定的权限提示
对于你总是允许的工具调用,跳过批准对话框。以下示例自动批准 ExitPlanMode——这是 Claude 在完成展示计划、请求继续时调用的工具——这样每次计划就绪时你都不会被提示。
与上面基于退出码的示例不同,自动批准要求你的钩子向 stdout 写入一份 JSON 决策。PermissionRequest 钩子会在 Claude Code 即将显示一个权限对话框时触发,返回 "behavior": "allow" 就相当于代替你回答了它。
该匹配器把这个钩子限定为只针对 ExitPlanMode,因此不会影响其他提示。把它添加到 ~/.claude/settings.json:
当该钩子批准时,Claude Code 会退出计划模式,并恢复你进入计划模式之前生效的权限模式。记录中会在原本会显示对话框的地方显示“Allowed by PermissionRequest hook”。钩子这条路径始终保留当前对话:它无法像对话框那样清空上下文并开始一个全新的实施会话。
要改为设置一个特定的权限模式,你钩子的输出可以包含一个带 setMode 条目的 updatedPermissions 数组。mode 值可以是任意权限模式,例如 default、acceptEdits 或 bypassPermissions,destination: "session" 只会将其应用于当前会话。
bypassPermissions 只在该会话启动时就已经可以使用绕过模式的情况下才生效:即通过 --dangerously-skip-permissions、--permission-mode bypassPermissions、--allow-dangerously-skip-permissions,或设置中的 permissions.defaultMode: "bypassPermissions",且未被 permissions.disableBypassPermissionsMode 禁用。它绝不会被持久化为 defaultMode。
要将该会话切换到 acceptEdits,你的钩子应向 stdout 写入以下 JSON:
请让匹配器尽可能窄。匹配 .* 或留空该匹配器,会自动批准每一个权限提示,包括文件写入和 shell 命令。完整的决策字段集,请参阅PermissionRequest 参考文档。
钩子的工作原理
钩子事件会在 Claude Code 生命周期的特定节点触发。当某个事件触发时,所有匹配的钩子会并行运行,完全相同的钩子命令会被自动去重。下表展示了每个事件及其触发时机:
| 事件 | 触发时机 |
|---|---|
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 发送一条通知时 |
MessageDisplay | 助手消息文本正在显示期间 |
SubagentStart | 某个子智能体被生成时 |
SubagentStop | 某个子智能体完成时 |
TaskCreated | 某个任务正通过 TaskCreate 被创建时 |
TaskCompleted | 某个任务正被标记为已完成时 |
Stop | Claude 完成回复时 |
StopFailure | 该轮次因 API 错误而结束时。输出和退出码都会被忽略 |
TeammateIdle | 某个智能体团队成员即将闲置时 |
InstructionsLoaded | 某个 CLAUDE.md 或 .claude/rules/*.md 文件被加载进上下文时。会在会话启动时以及会话期间惰性加载文件时触发 |
ConfigChange | 会话期间某个配置文件发生变化时 |
CwdChanged | 工作目录发生变化时,例如 Claude 执行了一次 cd 命令。适合用像 direnv 这样的工具进行响应式环境管理 |
FileChanged | 磁盘上某个被监视的文件发生变化时。matcher 字段指定要监视哪些文件名 |
WorktreeCreate | 正通过 --worktree 或 isolation: "worktree" 创建一个 worktree 时。会取代默认的 git 行为 |
WorktreeRemove | 正在移除一个 worktree 时,无论是在会话退出时,还是某个子智能体完成时 |
PreCompact | 上下文压缩之前 |
PostCompact | 上下文压缩完成之后 |
Elicitation | 某个 MCP 服务器在工具调用期间请求用户输入时 |
ElicitationResult | 用户响应某个 MCP 征询之后、该回复被发送回该服务器之前 |
SessionEnd | 某个会话结束时 |
每个钩子都有一个决定其运行方式的 type。大多数钩子使用 "type": "command",运行一条 shell 命令。还有其他四种类型:
"type": "http":将事件数据以 POST 方式发往一个网址。请参阅HTTP 钩子。"type": "mcp_tool":调用一个已连接 MCP 服务器上的工具。请参阅MCP 工具钩子。"type": "prompt":单轮大模型评估。请参阅基于提示词的钩子。"type": "agent":带工具访问权限的多轮验证。智能体钩子是实验性功能,可能会发生变化。请参阅基于智能体的钩子。
合并多个钩子的结果
当多个钩子匹配同一个事件时,每个钩子的命令都会运行到完成,然后 Claude Code 才会合并这些结果。一个钩子返回 deny,不会阻止其他钩子继续执行。不要依赖某个钩子的 deny 来抑制另一个钩子的副作用。
所有匹配的钩子都完成之后,Claude Code 会合并它们的输出。对于 PreToolUse 的权限决策,最严格的答案会生效,优先级顺序为 deny、defer、ask、allow。来自 additionalContext 的文本会保留每个钩子的内容,一起传给 Claude。
以下示例在 Bash 上注册了两个 PreToolUse 钩子。第一个会将每条命令追加到一个日志文件,并以 0 退出。第二个运行一个脚本,当命令中包含 rm -rf 时以 2 退出拒绝:
当 Claude 尝试运行 rm -rf /tmp/build 时,两个钩子会并行执行。日志钩子把该命令写入 ~/.claude/bash.log 并以 0 退出,即不表达任何决策。防护钩子以 2 退出,拒绝该次工具调用。拒绝优先,因此 Claude Code 会阻止该命令,并向 Claude 显示防护钩子的 stderr。由于日志钩子已经运行过,这条日志记录仍会被写入。
读取输入并返回输出
钩子通过 stdin、stdout、stderr 和退出码与 Claude Code 通信。当某个事件触发时,Claude Code 会把事件特定的数据以 JSON 形式传给你脚本的 stdin。你的脚本读取这份数据,完成工作,并通过退出码告诉 Claude Code 接下来该怎么做。
钩子输入
每个事件都包含像 session_id 和 cwd 这样的通用字段,但每种事件类型还会附加不同的数据。例如,当 Claude 运行一条 Bash 命令时,PreToolUse 钩子会在 stdin 上收到类似这样的内容:
你的脚本可以解析这份 JSON 并针对其中任何字段采取行动。UserPromptSubmit 钩子会改为获得 prompt 文本,SessionStart 钩子会获得一个值为 startup、resume、clear 或 compact 的 source,以此类推。关于共享字段,请参阅参考文档中的通用输入字段,关于各事件特定的模式,请参阅其各自的章节。
钩子输出
你的脚本通过写入 stdout 或 stderr,并以特定的退出码退出,来告诉 Claude Code 接下来该怎么做。以下 PreToolUse 钩子会阻止一条命令:
退出码决定了接下来会发生什么:
- 退出码 0:该钩子表示没有异议,该操作正常继续。对于
PreToolUse钩子,这并不等于批准该次工具调用:正常的权限流程仍然适用。对于UserPromptSubmit、UserPromptExpansion和SessionStart钩子,你写入 stdout 的任何内容都会被加入 Claude 的上下文。 - 退出码 2:该操作被阻止。把原因写入 stderr,Claude 会将其作为反馈收到,从而可以调整方案。有些事件无法被阻止:对于
SessionStart、Setup、Notification等事件,退出码 2 会向用户显示 stderr,执行会继续。完整列表请参阅各事件的退出码 2 行为。 - 任何其他退出码:该操作继续。记录中会显示一条
<hook name> hook error提示,随后是 stderr 的第一行;完整的 stderr 会写入调试日志。
结构化 JSON 输出
退出码只能让你阻止或保持沉默。要获得更精细的控制,可以改为退出码 0,并向 stdout 打印一个 JSON 对象。
用退出码 2 配合一条 stderr 消息来阻止,或用退出码 0 配合 JSON 实现结构化控制。不要混用两者:当你以退出码 2 退出时,Claude Code 会忽略 JSON。
例如,一个 PreToolUse 钩子可以拒绝某次工具调用并告诉 Claude 原因,或将其升级请求用户批准:
使用 "deny" 时,Claude Code 会取消该次工具调用,并将 permissionDecisionReason 反馈给 Claude。以下这些 permissionDecision 值专属于 PreToolUse:
"allow":跳过交互式权限提示。拒绝和询问规则(包括企业统一管理的拒绝列表)仍然适用"deny":取消该次工具调用,并把原因发给 Claude"ask":像平常一样向用户显示权限提示
第四个值 "defer",可在带 -p 标志的非交互模式中使用。它会退出该进程,同时保留该次工具调用,以便某个 Agent SDK 包装层可以收集输入并恢复。请参阅参考文档中的延后处理一次工具调用。
返回 "allow" 会跳过交互式提示,但不会覆盖权限规则。如果某条拒绝规则匹配该次工具调用,即使你的钩子返回了 "allow",该次调用仍会被阻止。如果某条询问规则匹配,用户仍会被提示。这意味着任何设置范围(包括统一管理设置)中的拒绝规则,始终优先于钩子的批准。
其他事件使用不同的决策模式。例如,PostToolUse 和 Stop 钩子使用一个顶层的 decision: "block" 字段,而 PermissionRequest 使用 hookSpecificOutput.decision.behavior。按事件分类的完整说明,请参阅参考文档中的汇总表。
对于 UserPromptSubmit 钩子,请改用 additionalContext 将文本注入 Claude 的上下文。
type: "prompt" 的钩子对输出的处理方式不同:请参阅基于提示词的钩子。
用匹配器筛选钩子
没有匹配器时,一个钩子会在其事件的每一次发生时触发。匹配器让你能缩小这个范围。例如,如果你只想在文件编辑之后运行一个格式化工具,而不是在每次工具调用之后都运行,请为你的 PostToolUse 钩子添加一个匹配器:
"Edit|Write" 匹配器只在 Claude 使用 Edit 或 Write 工具时触发,而不会在它使用 Bash、Read 或任何其他工具时触发。关于纯文本名称和正则表达式如何被求值,请参阅匹配器模式。
Claude 也可以通过 Bash 工具运行 shell 命令来创建或修改文件。如果你的钩子必须捕获每一次文件变化(例如用于合规扫描或审计日志),请添加一个Stop 钩子,让它每轮扫描一次工作树。如果需要按每次调用覆盖,也要匹配 Bash,并让你的脚本用 git status --porcelain 列出已修改和未跟踪的文件。
每种事件类型都基于一个特定字段进行匹配:
| 事件 | 匹配器筛选的内容 | 匹配器取值示例 |
|---|---|---|
PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied | 工具名称 | Bash、Edit|Write、mcp__.* |
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、agent_needs_input、agent_completed |
SubagentStart | 智能体类型 | general-purpose、Explore、Plan,或自定义智能体名称 |
PreCompact、PostCompact | 触发压缩的原因 | manual、auto |
SubagentStop | 智能体类型 | 与 SubagentStart 相同的取值 |
ConfigChange | 配置来源 | user_settings、project_settings、local_settings、policy_settings、skills |
StopFailure | 错误类型 | rate_limit、overloaded、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 |
Elicitation | MCP 服务器名称 | 你已配置的 MCP 服务器名称 |
ElicitationResult | MCP 服务器名称 | 与 Elicitation 相同的取值 |
FileChanged | 要监视的字面文件名(参阅 FileChanged) | .envrc|.env |
UserPromptExpansion | 命令名称 | 你的技能或命令名称 |
UserPromptSubmit、PostToolBatch、Stop、TeammateIdle、TaskCreated、TaskCompleted、WorktreeCreate、WorktreeRemove、CwdChanged、MessageDisplay | 不支持匹配器 | 每次发生都会触发 |
以下标签页展示了在不同事件类型上使用的更多匹配器。
- 记录每一条 Bash 命令
- 匹配 MCP 工具
- 会话结束时清理
只匹配 Bash 工具调用,并将每条命令记录到一个文件中。PostToolUse 事件会在命令完成后触发,因此 tool_input.command 中包含实际运行的内容。该钩子通过 stdin 以 JSON 形式接收事件数据,jq -r '.tool_input.command' 只提取命令字符串,>> 将其追加到日志文件中:
关于完整的匹配器语法,请参阅钩子参考文档。
用 if 字段按工具名称和参数筛选
if 字段需要 Claude Code v2.1.85 或更高版本。更早的版本会忽略它,并在每次匹配的调用上运行该钩子。
if 字段使用权限规则语法,同时按工具名称和参数筛选钩子,因此只有当该次工具调用匹配时,该钩子进程才会启动。这超出了 matcher 的能力范围,因为 matcher 只在分组级别按工具名称筛选。
例如,以下配置只在 Claude 使用 git 命令时运行一个钩子,而不是对所有 Bash 命令都运行:
你的钩子命令是否运行,取决于你 if 模式的形状以及 Claude 正在调用的 Bash 命令:
if 模式 | Bash 命令 | 钩子是否运行? | 原因 |
|---|---|---|---|
Bash(git *) | git push | 是 | 命令名称匹配 |
Bash(git *) | npm test && git push | 是 | 每个子命令都会被检查;git push 匹配 |
Bash(git *) | echo $(git log) | 是 | $() 和反引号内部的命令也会被检查;git log 匹配 |
Bash(git *) | echo $(date) | 否 | 没有子命令匹配 git * |
Bash(git push *) | echo $(date) | 是 | 指定了超出命令名称的模式,无论如何都会在遇到 $()、反引号或 $VAR 时运行该钩子 |
当某条 Bash 命令无法被解析时,该筛选还会失效开放(fail open),无论模式如何都会运行你的钩子。由于这个筛选是尽力而为的,请使用权限系统而不是钩子来强制执行硬性的允许或拒绝。
if 字段接受与权限规则相同的模式:"Bash(git *)"、"Edit(*.ts)" 等等。要匹配多个工具名称,可以使用各自带有独立 if 值的独立处理器,或在支持竖线并列的 matcher 级别进行匹配。
if 只对工具类事件有效:PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest 和 PermissionDenied。将其添加到任何其他事件都会阻止该钩子运行。
配置钩子位置
你添加钩子的位置决定了它的范围:
| 位置 | 范围 | 是否可共享 |
|---|---|---|
~/.claude/settings.json | 你的所有项目 | 否,仅限本机 |
.claude/settings.json | 单个项目 | 是,可以提交到仓库 |
.claude/settings.local.json | 单个项目 | 否,Claude Code 创建它时会被 gitignore |
| 统一管理策略设置 | 组织范围 | 是,由管理员控制 |
插件 的 hooks/hooks.json | 插件启用期间 | 是,随插件打包 |
| 技能或智能体 frontmatter | 该技能或智能体活动期间 | 是,定义在该组件文件中 |
在 Claude Code 中运行 /hooks,可按事件分组浏览所有已配置的钩子。
要关闭钩子,在你的设置文件中设置 "disableAllHooks": true。在统一管理设置中配置的钩子仍会运行,除非那里也设置了 disableAllHooks。
如果你在 Claude Code 运行期间直接编辑设置文件,文件监视器通常会自动获取钩子的变化。
基于提示词的钩子
对于需要判断力而非确定性规则的决策,使用 type: "prompt" 钩子。Claude Code 不会运行一条 shell 命令,而是把你的提示词和该钩子的输入数据发送给一个 Claude 模型(默认是 Haiku)来做决定。如果你需要更强的能力,可以用 model 字段指定一个不同的模型。
该模型唯一的任务是以 JSON 形式返回一个是/否决策:
"ok": true:该操作继续"ok": false:接下来会发生什么取决于事件:Stop和SubagentStop:reason会反馈给 Claude,让它继续工作PreToolUse:该次工具调用被拒绝,reason会作为工具错误返回给 Claude,让它可以调整并继续PostToolUse、PostToolBatch、UserPromptSubmit和UserPromptExpansion:该轮次结束,reason会以一行警告的形式出现在聊天中
以下示例使用一个 Stop 钩子询问模型所有请求的任务是否已完成。如果模型返回 "ok": false,Claude 会继续工作,并把 reason 作为它的下一条指令:
关于完整的配置选项,请参阅参考文档中的基于提示词的钩子。
基于智能体的钩子
当验证需要检查文件或运行命令时,使用 type: "agent" 钩子。与只进行一次大模型调用的提示词钩子不同,智能体钩子会生成一个子智能体,它可以读取文件、搜索代码,并使用其他工具在返回决策之前验证条件。
智能体钩子使用与提示词钩子相同的 "ok" / "reason" 响应格式,但默认超时时间更长,为 60 秒,最多允许 50 轮工具使用。
以下示例在允许 Claude 停止之前,验证测试是否通过:
当钩子的输入数据本身足以做出决定时,使用提示词钩子。当你需要针对代码库的实际状态进行验证时,使用智能体钩子。
关于完整的配置选项,请参阅参考文档中的基于智能体的钩子。
HTTP 钩子
使用 type: "http" 钩子,将事件数据以 POST 方式发往一个 HTTP 端点,而不是运行一条 shell 命令。该端点会收到与命令钩子在 stdin 上收到的相同的 JSON,并通过 HTTP 响应体、以相同的 JSON 格式返回结果。
当你希望由一个 Web 服务器、云函数或外部服务来处理钩子逻辑时,HTTP 钩子会很有用:例如,一个跨团队记录工具使用事件的共享审计服务。
以下示例将每次工具使用都 POST 到一个本地日志服务:
该端点应返回一个使用与命令钩子相同的输出格式的 JSON 响应体。要阻止某次工具调用,返回一个带有恰当 hookSpecificOutput 字段的 2xx 响应。单靠 HTTP 状态码无法阻止操作。
请求头的值支持用 $VAR_NAME 或 ${VAR_NAME} 语法进行环境变量插值。只有 allowedEnvVars 数组中列出的变量会被解析;所有其他 $VAR 引用会保持为空。
关于完整的配置选项和响应处理,请参阅参考文档中的HTTP 钩子。
限制与故障排查
限制
设计钩子时请牢记以下约束:
- 命令钩子只通过 stdout、stderr 和退出码通信。它们无法触发
/命令或工具调用。通过additionalContext返回的文本会作为一条系统提醒被注入,Claude 会将其当作纯文本读取。HTTP 钩子则改为通过响应体通信。 - 钩子超时时间因类型而异。可以用
timeout字段(单位秒)对单个钩子进行覆盖。command、http、mcp_tool:10 分钟。UserPromptSubmit会将其降为 30 秒,MessageDisplay会将其降为 10 秒。prompt:30 秒。agent:60 秒。
PostToolUse钩子无法撤销操作,因为该工具已经执行过了。PermissionRequest钩子在带-p标志的非交互模式下不会触发。请用PreToolUse钩子实现自动化的权限决策。Stop钩子只会在 Claude 完成回复时触发,而不仅仅是在任务完成时。它们不会在用户中断时触发。API 错误会改为触发 StopFailure。- 当多个
PreToolUse钩子返回updatedInput来重写某个工具的参数时,最后完成的那个会生效。由于钩子是并行运行的,这个顺序是不确定的。请避免让多个钩子修改同一个工具的输入。
钩子与权限模式
PreToolUse 钩子会在任何权限模式检查之前触发。一个返回 permissionDecision: "deny" 的钩子,即使在 bypassPermissions 模式下,或使用了 --dangerously-skip-permissions,也会阻止该工具。这让你可以强制执行用户无法通过更改权限模式来绕过的策略。
反过来则不成立:一个返回 "allow" 的钩子不会绕过设置中的拒绝规则。钩子可以收紧限制,但不能放宽到超出权限规则所允许的范围。
钩子没有触发
该钩子已配置,但从未执行。
- 运行
/hooks,确认该钩子出现在正确的事件下 - 检查该匹配器模式是否与工具名称精确匹配。匹配器区分大小写
- 确认你触发的是正确的事件类型:
PreToolUse在工具执行之前触发,PostToolUse在之后触发 - 如果在带
-p标志的非交互模式下使用PermissionRequest钩子,请改用PreToolUse
输出中出现钩子错误
你在记录中看到类似“PreToolUse hook error: ...”的消息。
- 你的脚本意外以非零退出码退出。通过传入示例 JSON 手动测试它:
- 如果你看到“command not found”,请使用绝对路径或
${CLAUDE_PROJECT_DIR}来引用脚本。要完全避免 shell 引号问题,添加"args": [],切换到exec 形式,直接生成该脚本,不经过 shell - 如果你看到“jq: command not found”,请安装
jq,或使用 Python/Node.js 进行 JSON 解析 - 如果该脚本根本没有运行,请将其设为可执行:
chmod +x ./my-hook.sh
/hooks 显示没有配置任何钩子
你编辑了一个设置文件,但该钩子没有出现在菜单中。
- 文件编辑通常会被自动获取。如果几秒后仍未出现,文件监视器可能错过了这次变化:重启你的会话以强制重新加载。
- 验证你的 JSON 是否有效:不允许有尾随逗号和注释
- 确认设置文件位于正确的位置:项目钩子用
.claude/settings.json,全局钩子用~/.claude/settings.json
Stop 钩子触及阻止上限
Claude 持续工作而不停止,然后以一条警告结束该轮次,说明 Stop 钩子连续阻止的次数过多。
Claude Code 会在一个 Stop 钩子连续阻止八次而没有进展后覆盖它。你的钩子脚本需要检查它是否已经触发过一次续接。从 JSON 输入中解析 stop_hook_active 字段,如果为 true 则提前退出:
如果你的钩子确实需要超过八次迭代才能收敛,可以用 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 提高这个上限。
JSON 校验失败
即使你的钩子脚本输出了有效的 JSON,Claude Code 仍显示一个 JSON 解析错误。
当 Claude Code 运行一个 shell 形式的命令钩子(即不带 args 的钩子)时,默认会在 macOS 和 Linux 上生成 sh -c,或在 Windows 上生成 Git Bash。这个 shell 是非交互式的,但 Git Bash 以及某些配置(例如 BASH_ENV 指向 ~/.bashrc)仍会加载你的配置文件。如果该配置文件中包含无条件的 echo 语句,其输出会被添加到你钩子 JSON 的前面:
Claude Code 会尝试将其解析为 JSON 并失败。要修复这个问题,在你的 shell 配置文件中包裹 echo 语句,使其只在交互式 shell 中运行:
$- 变量包含 shell 标志,i 表示交互式。钩子运行在非交互式 shell 中,因此该 echo 会被跳过。
调试技巧
用 Ctrl+O 切换的记录视图,会为每个触发的钩子显示一行摘要:成功时静默,阻止性错误显示 stderr,非阻止性错误显示一条 <hook name> hook error 提示,随后是 stderr 的第一行。
要查看完整的执行详情(包括哪些钩子匹配、它们的退出码、stdout 和 stderr),请阅读调试日志。用 claude --debug-file /tmp/claude.log 启动 Claude Code,将其写入一个已知路径,然后在另一个终端中用 tail -f /tmp/claude.log。如果你启动时没有加这个标志,可以在会话中途运行 /debug 启用日志记录并找到日志路径。
了解更多
- 钩子参考文档:完整的事件模式、JSON 输出格式、异步钩子和 MCP 工具钩子
- 安全考量:在共享或生产环境中部署钩子之前请先阅读
- Bash 命令校验器示例:完整的参考实现