设置你的第一个 hook
要创建 hook,请将hooks 块添加到 设置文件。本演练创建一个桌面通知 hook,这样每当 Claude 等待你的输入而不是监视终端时,你都会收到警报。
1
将 hook 添加到你的设置
打开 如果你的设置文件已经有一个 您也可以通过在 CLI 中描述您想要的内容来要求 Claude 为您编写 hook。
~/.claude/settings.json 并添加一个 Notification hook。如果文件不存在,请创建它。下面的示例使用 osascript 用于 macOS;有关 Linux 和 Windows 命令,请参阅 在 Claude 需要输入时获得通知。hooks 键,请将 Notification 作为现有事件键的同级添加,而不是替换整个对象。每个事件名称是单个 hooks 对象内的一个键:2
验证配置
在 Claude Code 输入框中输入
/hooks 以打开 hook 浏览器。您的新 hook 会出现在 Notification 下的列表中。3
测试 hook
按
Esc 返回 CLI。按 Shift+Tab 直到状态栏显示 ⏸ manual mode on,要求 Claude 做需要权限的事情,然后切换离开终端。您应该会收到桌面通知。你可以自动化什么
Hooks 让你在 Claude Code 生命周期中的关键点运行代码:编辑后格式化文件、在执行前阻止命令、在 Claude 需要输入时发送通知、在会话开始时注入上下文等。有关完整的 hook 事件列表,请参阅 Hooks 参考。 每个示例都包含一个现成的配置块,你可以将其添加到 设置文件。 有关运行单独模型审查并将发现反馈回会话的 hooks 的生产示例,请参阅security-guidance 插件如何与 Claude Code 集成。
在 Claude 需要输入时获得通知
每当 Claude 完成工作并需要你的输入时获得桌面通知,这样你可以切换到其他任务而无需检查终端。 此 hook 使用Notification 事件,Claude Code 会在 Claude 等待输入或权限时触发该事件。请参阅每个通知类型何时触发以了解确切的时间。
下面的每个选项卡使用平台的原生通知命令。将其添加到 ~/.claude/settings.json:
- macOS
- Linux
- Windows (PowerShell)
如果没有通知出现
如果没有通知出现
osascript 通过内置的 Script Editor 应用程序路由通知。如果 Script Editor 没有通知权限,命令会静默失败,macOS 也不会提示您授予该权限。在 Terminal 中运行一次以下命令,使 Script Editor 出现在您的通知设置中:matcher 对所有通知类型触发。要仅在特定事件上触发,请将其设置为以下值之一:
Claude Code 在终端和通过 Agent SDK 回答权限请求的 Claude Desktop、VS Code 扩展和其他主机中对
permission_prompt 的时间不同。请参阅每个通知类型何时触发以了解两种时间。
quota_auto_resume_fired、quota_auto_resume_stale 和 quota_auto_resume_disabled 匹配器需要 Claude Code v2.1.234 或更高版本。
在终端会话中,沙箱命令的网络请求的 permission_prompt 需要 Claude Code v2.1.246 或更高版本。
队友的终端设置问题的 agent_needs_input 需要 Claude Code v2.1.248 或更高版本。
在 Claude Code 输入框中输入 /hooks,并确认该 hook 出现在 Notification 下。
编辑后自动格式化代码
在 Claude 编辑的每个文件上自动运行 Prettier,以便格式保持一致而无需手动干预。 此 hook 使用带有Edit|Write 匹配器的 PostToolUse 事件,因此它仅在文件编辑工具之后运行。该命令使用 jq 提取编辑的文件路径并将其传递给 Prettier。将其添加到项目根目录中的 .claude/settings.json:
Bash 命令重写它时,请改用 FileChanged hook。
本页上的 Bash 示例使用
jq 进行 JSON 解析。使用 brew install jq(macOS)、apt-get install jq(Debian 和 Ubuntu)安装它,或参阅 jq 下载。阻止对受保护文件的编辑
防止 Claude 修改敏感文件,如.env、package-lock.json 或 .git/ 中的任何内容。Claude 会收到解释编辑被阻止原因的反馈,因此它可以调整其方法。
此示例使用 hook 调用的单独脚本文件。该脚本根据受保护模式列表检查目标文件路径,并以代码 2 退出以阻止编辑。
1
创建 hook 脚本
将其保存到
.claude/hooks/protect-files.sh:2
使脚本可执行(macOS 和 Linux)
Hook 脚本必须可执行才能让 Claude Code 运行它们:
3
注册 hook
将
PreToolUse hook 添加到 .claude/settings.json,在任何 Edit 或 Write 工具调用之前运行脚本:4
测试 hook
要求 Claude 向你的
.env 文件添加注释。Claude Code 在它运行前阻止编辑,并将脚本的 Blocked: 消息作为反馈传递给 Claude。压缩后重新注入上下文
当 Claude 的上下文窗口填满时,压缩会总结对话以释放空间。这可能会丢失重要细节。使用带有compact 匹配器的 SessionStart hook 在每次压缩后重新注入关键上下文。
Claude Code 将你的命令写入 stdout 的任何纯文本添加到 Claude 的上下文中。此示例提醒 Claude 项目约定和最近的工作。将其添加到项目根目录中的 .claude/settings.json:
echo,如 git log --oneline -5 来显示最近的提交。有关在每个会话开始时注入上下文,请考虑改用 CLAUDE.md。有关环境变量,请参阅参考中的 CLAUDE_ENV_FILE。
审计配置更改
跟踪会话期间设置或 skills 文件何时更改。ConfigChange 事件在外部进程或编辑器修改配置文件时触发,因此你可以记录更改以进行合规性检查或阻止未授权的修改。
此示例将每个更改附加到审计日志。将其添加到 ~/.claude/settings.json:
user_settings、project_settings、local_settings、policy_settings 或 skills。要阻止更改生效,以代码 2 退出或返回 {"decision": "block"}。有关完整的输入架构,请参阅 ConfigChange 参考。
要确认 hook 记录更改,请在会话运行时在另一个编辑器中编辑设置文件,然后打开 ~/claude-config-audit.log:hook 为每个更改附加一个 JSON 行,包含时间戳、源和文件路径。
当目录或文件更改时重新加载环境
某些项目根据你所在的目录设置不同的环境变量。direnv 之类的工具在你的 shell 中自动执行此操作,但 Claude 的 Bash 工具不会自动拾取这些更改。 配对SessionStart hook 和 CwdChanged hook 可以解决这个问题。SessionStart 加载你启动时所在目录的变量,CwdChanged 在 Claude 每次更改目录时重新加载它们。两者都写入 CLAUDE_ENV_FILE,Claude Code 在每个 Bash 命令之前作为脚本前导运行。将其添加到 ~/.claude/settings.json:
.envrc 的目录中运行一次 direnv allow,以便 direnv 被允许加载它。如果你使用 devbox 或 nix 而不是 direnv,相同的模式适用于 devbox shellenv 或 devbox global shellenv 代替 direnv export bash。
要对特定文件而不是每个目录更改做出反应,请使用 FileChanged 和 matcher 列出要监视的文件名,用 | 分隔。要构建监视列表,Claude Code 将此值分割为文字文件名而不是作为正则表达式进行评估。有关当文件更改时相同值如何也过滤哪些 hook 组运行,请参阅 FileChanged。此示例监视工作目录中 .envrc 和 .env 的更改:
watchPaths 输出和 CLAUDE_ENV_FILE 详情,请参阅 CwdChanged 和 FileChanged 参考条目。
自动批准特定权限提示
跳过你总是允许的工具调用的批准对话。此示例自动批准ExitPlanMode,这是 Claude 在完成呈现计划并要求继续时调用的工具,因此你不会在每次计划准备好时被提示。
与上面的退出代码示例不同,自动批准需要你的 hook 将 JSON 决策写入 stdout。Claude Code 运行 PermissionRequest hooks 当它即将要求你的权限时,如果你的 hook 返回 "behavior": "allow",Claude Code 代表你回答请求。
匹配器将 hook 的范围限制为仅 ExitPlanMode,因此没有其他提示受到影响。将其添加到 ~/.claude/settings.json:
updatedPermissions 数组,其中包含 setMode 条目。mode 值是任何权限模式,如 default、acceptEdits 或 bypassPermissions,destination: "session" 仅将其应用于当前会话。
bypassPermissions 仅在你启动会话时绕过模式已经可用的情况下才适用:--dangerously-skip-permissions、--permission-mode bypassPermissions、--allow-dangerously-skip-permissions 或 用户设置、--settings 或托管设置 中的 permissions.defaultMode: "bypassPermissions"。如果绕过模式已被 permissions.disableBypassPermissionsMode 禁用,或你是在受限模式中启动会话,则它不适用。Claude Code 永远不会将其保存为 defaultMode。acceptEdits,你的 hook 将此 JSON 写入 stdout:
.* 或留下匹配器为空会自动批准每个工具权限提示,包括文件写入和 shell 命令。有关完整的决策字段集,请参阅 PermissionRequest 参考。
Hooks 如何工作
Claude Code 在其生命周期中的特定点触发 hook 事件。当事件触发时,Claude Code 会并行运行所有匹配的 hook;有关重复处理程序如何处理的信息,请参阅 Hook 处理程序字段。下表显示每个事件及其触发时间:
每个 hook 都有一个
type 来确定它如何运行。大多数 hooks 使用 "type": "command",它运行 shell 命令。还有四种其他类型可用:
"type": "http":将事件数据 POST 到 URL。请参阅 HTTP hooks。"type": "mcp_tool":在已配置的 MCP 服务器上调用工具。请参阅 MCP tool hooks。"type": "prompt":单轮 LLM 评估。请参阅 Prompt-based hooks。"type": "agent":具有工具访问权限的多轮验证。Agent hooks 是实验性的,可能会改变。请参阅 Agent-based hooks。
合并来自多个 hooks 的结果
当多个 hooks 匹配同一事件时,每个 hook 的命令都会运行到完成,然后 Claude Code 合并结果。一个 hook 返回deny 不会阻止兄弟 hooks 执行。不要依赖一个 hook 的 deny 来抑制另一个 hook 中的副作用。
所有匹配的 hooks 完成后,Claude Code 合并它们的输出。对于 PreToolUse 权限决策,最严格的答案获胜,顺序为 deny、defer、ask、allow。来自 additionalContext 的文本从每个 hook 保留并一起传递给 Claude。
下面的示例在 Bash 上注册两个 PreToolUse hooks。第一个将每个命令附加到日志文件并以 0 退出。第二个运行一个脚本,当命令包含 rm -rf 时以 2 退出以拒绝:
rm -rf /tmp/build 时,两个 hooks 并行执行。日志 hook 将命令写入 ~/.claude/bash.log 并以 0 退出,这表示没有决策。防护栏 hook 以 2 退出,这拒绝了工具调用。拒绝获胜,所以 Claude Code 阻止命令并向 Claude 显示防护栏的 stderr。日志条目仍然被写入,因为日志 hook 已经运行。
读取输入并返回输出
Hooks 通过 stdin、stdout、stderr 和退出代码与 Claude Code 通信。当事件触发时,Claude Code 将事件特定的数据作为 JSON 传递到脚本的 stdin。你的脚本读取该数据,完成其工作,并通过退出代码告诉 Claude Code 接下来要做什么。Hook 输入
每个事件都包含常见字段,如session_id(会话的唯一 ID)和 cwd(事件触发时的工作目录),但每个事件类型添加不同的数据。当 Claude 运行 Bash 命令时,PreToolUse hook 在 stdin 上接收这些字段:
hook_event_name:触发 hook 的事件tool_name:Claude 即将使用的工具tool_input:Claude 传递给工具的参数。对于 Bash,其command字段包含 shell 命令。
npm test 命令的 hook 输入看起来像这样:
UserPromptSubmit hooks 获取 prompt 文本,SessionStart hooks 获取 source(startup、resume、clear、compact 或 fork),等等。有关共享字段,请参阅参考中的 常见输入字段,以及每个事件的部分了解事件特定的架构。
Hook 输出
你的脚本通过写入 stdout 或 stderr 并以特定代码退出来告诉 Claude Code 接下来要做什么。以下PreToolUse hook 阻止一个命令:
onFailure 设置为 "block" 的 hook 就是一个例子。以 0 退出表示没有异议,以 2 退出则阻止操作:
- 退出 0:如果从
PreToolUsehook 以 0 退出,工具调用仍会经过正常的 权限流程,因为退出 0 并不会批准它。对于UserPromptSubmit、UserPromptExpansion、SessionStart和PostModelSwitchhook,Claude Code 会将其 视为纯文本 的 stdout 添加到 Claude 的上下文中。 - 退出 2:将原因写入 stderr。根据事件的不同,Claude 会将其作为反馈接收以便进行调整,或者向用户显示,或者不显示任何消息(如
ConfigChange和Elicitation)。某些事件无法被阻止:对于SessionStart等事件,退出 2 会向用户显示 stderr,并继续执行。有关完整列表,请参阅 每个事件的退出码 2 行为。 - 任何其他退出码:有关每个事件的例外情况(例如
WorktreeCreate在任何非零退出时都会失败),请参阅 退出码输出。对于其他事件,除非该 hook 属于失败时会阻止操作的 hook,否则请根据 hook 打印到 stdout 的内容找到对应的情况:- 通过 schema 验证的已解析对象:Claude Code 忽略退出码,仅由 JSON 决定结果,hook 不会被报告为错误。
- 未通过 schema 验证的已解析对象,或 Claude Code 尝试解析为 JSON 但不是有效 JSON 的 stdout:操作作为非阻止错误继续进行。请查看
<hook name> hook error通知中的验证或解析消息。 - Claude Code 视为纯文本 的 stdout,或空 stdout:操作作为非阻止错误继续进行。会话记录显示
<hook name> hook error通知,然后是 stderr 的第一行,前缀为Failed with non-blocking status code:。要捕获完整的 stderr,请使用claude --debug或在会话中运行/debug启用 调试日志。
结构化 JSON 输出
退出代码只让你阻止或保持沉默。为了获得更多控制,退出 0 并改为将 JSON 对象打印到 stdout。使用退出 2 以 stderr 消息阻止,或使用 JSON 退出 0 以获得结构化控制。每个 hook 选择一种方法。有关混合它们时会发生什么,请参阅 退出代码输出。
PreToolUse hook 可以拒绝工具调用并告诉 Claude 为什么,或将其升级给用户以获得批准:
"deny",Claude Code 取消工具调用并将 permissionDecisionReason 反馈给 Claude。
在 PreToolUse 上,还可以返回 "ask" 以向用户显示正常的权限提示,或返回 "allow" 以跳过交互式权限提示。使用 "allow" 时,拒绝和询问规则(包括企业托管拒绝列表)仍然适用,以下情况的提示也仍然适用:
- 从 网络路径 读取
-
标记为
requiresUserInteraction的 MCP 工具 -
在该设置到达 Claude Code 的会话中,您的组织设置为
ask的连接器工具 有关"allow"在计划模式下的作用,请参阅 计划模式下的 hook 批准。
"defer" 在 非交互模式 中使用 -p 标志时可用。返回该值会结束当前轮次并保留工具调用,以便 Agent SDK 包装器可以收集输入并恢复。请参阅参考中的 延迟工具调用以供稍后使用。
PreModelSwitch hook 返回相同的 permissionDecision 字段:"allow" 让模型切换进行,"deny" 取消它。"ask" 在你在交互式会话中运行 /model 时让你确认切换;在其他地方,Claude Code 将 "ask" 视为拒绝。请参阅 PreModelSwitch 决策控制。
其他事件使用不同的决策模式。例如,PostToolUse 和 Stop hooks 使用顶级 decision: "block" 字段,而 PermissionRequest 使用 hookSpecificOutput.decision.behavior。有关按事件的完整分解,请参阅参考中的 摘要表。
对于 UserPromptSubmit hooks,改用 hookSpecificOutput.additionalContext 将文本注入到 Claude 的上下文中。将 additionalContext 嵌套在 hookSpecificOutput 内;如果你将其放在 JSON 的顶级,Claude Code 会默默忽略它。例如,此输出将当前分支状态添加到每个提示:
type: "prompt" 的 Hooks 处理输出的方式不同:请参阅 Prompt-based hooks。
使用匹配器过滤 hooks
没有匹配器,hook 会在其事件的每次出现时触发。匹配器让你缩小范围。例如,如果你只想在文件编辑后运行格式化程序(而不是在每个工具调用后),将匹配器添加到你的PostToolUse hook:
"Edit|Write" 匹配器仅在 Claude 使用 Edit 或 Write 工具时触发,而不是在它使用 Bash、Read 或任何其他工具时触发。逗号以相同的方式分隔替代项,所以 "Edit, Write" 是等效的。请参阅 匹配器模式 了解纯名称和正则表达式如何被评估。
Claude 也可以通过运行 shell 命令来创建或修改文件。如果你的 hook 必须看到每个文件更改(例如用于合规性扫描或审计日志),添加一个
Stop hook,它每轮扫描一次工作树。为了获得每次调用的覆盖,也匹配 Bash|PowerShell 并让你的脚本使用 git status --porcelain 列出修改和未跟踪的文件。PowerShell hook 输入部分 解释了为什么仅匹配 Bash 是不够的。要在特定文件在磁盘上更改时运行 hook(无论是什么写入它),使用 FileChanged hook。
下面的选项卡显示不同事件类型上的更多匹配器示例。
- 记录每个 Bash 命令
- 匹配 MCP 工具
- 在会话结束时清理
仅匹配
Bash 工具调用并将每个命令记录到文件。PostToolUse 事件在命令完成后触发,因此 tool_input.command 包含运行的内容。hook 在 stdin 上接收事件数据作为 JSON,jq -r '.tool_input.command' 仅提取命令字符串,>> 将其附加到日志文件:使用 if 字段按工具名称和参数过滤
if 字段使用 权限规则语法 按工具名称和参数一起过滤 hooks,因此 hook 进程仅在工具调用匹配时生成。这超越了 matcher,它仅在工具名称级别按组过滤。
例如,这个配置仅在 Claude 使用 git 命令而不是所有 Bash 命令时运行 hook:
if 模式的形状和 Claude 正在调用的 Bash 命令:
当 Claude Code 无法确定 Bash 输入运行哪些命令时,它无论如何都会运行你的 hook。Bash 匹配表 涵盖 Claude Code 可以和不能按子命令缩小的命令形状。因为过滤器是尽力而为的,使用 权限系统 而不是 hook 来强制执行硬允许或拒绝。
if 字段接受与权限规则相同的模式:"Bash(git *)"、"Edit(*.ts)" 等。要匹配多个工具名称,使用单独的处理程序,每个都有自己的 if 值,或在 matcher 级别匹配,其中支持管道交替。
if 仅适用于工具事件:PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest 和 PermissionDenied。将其添加到任何其他事件会阻止 hook 运行。
配置 hook 位置
你添加 hook 的位置决定了其范围:
在 Claude Code 中运行
/hooks 以浏览所有按事件分组的配置 hooks。
要禁用 hooks,在设置文件中设置 "disableAllHooks": true。Claude Code 读取 设置优先级 应用后剩余的值,因此项目的设置文件可以覆盖你的。托管设置中配置的 Hooks 仍然运行,除非 disableAllHooks 也在那里设置。有关每个级别的完整范围,请参阅 disableAllHooks。
如果你在 Claude Code 运行时直接编辑设置文件,文件监视器通常会自动拾取 hook 更改。
基于提示的 hooks
对于需要判断而不是确定性规则的决策,使用type: "prompt" hooks。Claude Code 不运行 shell 命令,而是将你的提示和 hook 的输入数据发送到 Claude 模型来做出决策。如果你需要更多功能,可以使用 model 字段指定不同的模型。
模型的唯一工作是返回其决策作为 JSON:
"ok": true:操作继续"ok": false:发生的情况取决于事件:Stop和SubagentStop:reason被反馈给 Claude,以便它继续工作,除非响应也设置"impossible": true来标记该条件为永远无法满足的条件,在这种情况下 Claude Code 允许停止并且该轮次结束PreToolUse:工具调用被拒绝;默认情况下该轮次结束,拒绝reason在聊天中显示为警告行。在 hook 上设置continueOnBlock: true以改为将reason作为工具错误返回给 Claude,以便它可以调整并继续PostToolUse:默认情况下该轮次结束,reason在聊天中显示为警告行。设置continueOnBlock: true以将reason反馈给 Claude 并继续该轮次PostToolBatch、UserPromptSubmit和UserPromptExpansion:该轮次结束,reason在聊天中显示为警告行
Stop hook 询问模型是否所有请求的任务都已完成。如果模型返回 "ok": false 因为条件尚未满足,Claude 继续工作并使用 reason 作为其下一条指令:
基于代理的 hooks
当验证需要检查文件或运行命令时,使用type: "agent" hooks。与只进行单个 LLM 调用的提示 hooks 不同,代理 hooks 生成一个 subagent,它可以读取文件、搜索代码和使用其他工具来验证条件,然后返回决策。
代理 hooks 使用 "ok" / "reason" 响应格式,默认超时更长(60 秒),最多支持 50 个工具使用轮次。它们不支持提示 hook 的 impossible 字段。当 ok: false 时,Claude Code 处理代理 hook 的方式与处理在同一事件上设置 continueOnBlock: true 的提示 hook 相同,因此在 PreToolUse 和 PostToolUse 上轮次继续;代理 hooks 没有 continueOnBlock 字段。有关字段的详细信息,请参阅 代理 hook 配置,包括 Claude Code 用 hook 的 JSON 输入替换的 $ARGUMENTS 占位符。
此示例验证在允许 Claude 停止之前测试通过:
HTTP hooks
使用type: "http" hooks 将事件数据 POST 到 HTTP 端点,而不是运行 shell 命令。端点接收命令 hook 在 stdin 上接收的相同 JSON,并使用相同的 JSON 格式通过 HTTP 响应体返回结果。
HTTP hooks 在你想要 web 服务器、云函数或外部服务处理 hook 逻辑时很有用:例如,一个跨团队记录工具使用事件的共享审计服务。
此示例将每个工具使用 POST 到本地日志服务:
- 2xx 状态:要阻止工具调用,请在响应体中返回相应的
hookSpecificOutput字段。 - 任何其他状态,或请求失败:Claude Code 会报告非阻塞错误并允许操作继续。要让失败的端点阻止该操作,请在 hook 上设置
onFailure: "block"。
$VAR_NAME 或 ${VAR_NAME} 语法的环境变量插值。仅解析 allowedEnvVars 数组中列出的变量;所有其他 $VAR 引用保持为空。
有关完整的配置选项和响应处理,请参阅参考中的 HTTP hooks。
限制和故障排除
限制
设计 hooks 时请记住这些约束:- 命令 hooks 仅通过 stdout、stderr 和退出代码通信。它们无法触发
/命令或工具调用。通过additionalContext返回的文本被注入为 Claude 作为纯文本读取的系统提醒。HTTP hooks 改为通过响应体通信。 - Hook 超时因类型而异。通过
timeout字段(以秒为单位)按 hook 覆盖。command、http、mcp_tool:10 分钟。Claude Code 对UserPromptSubmit、PreModelSwitch和PostModelSwitchhooks 将此默认值降低到 30 秒,对MessageDisplay降低到 10 秒。prompt:30 秒。agent:60 秒。SessionEnd任何类型的 hooks 共享 1.5 秒的预算。如果你的设置为每个 hook 设置了更长的timeout,Claude Code 会将预算提高到匹配,最多 60 秒。
PostToolUsehooks 无法撤销操作,因为工具已经执行。PermissionRequesthook 在 Claude Code 即将向您请求权限时触发,或在它原本会自动拒绝无法显示提示的调用时触发。在带-p标志的非交互模式中,它们在dontAsk模式之外仍会运行,而没有 hook 做出决策且无其他方式可以应答的调用会被拒绝。Stophooks 在 Claude 完成响应时触发,而不仅仅在任务完成时。它们不在用户中断时触发。API 错误触发 StopFailure 代替。- 当多个
PreToolUsehooks 返回updatedInput来重写工具的参数时,最后完成的获胜。由于 hooks 并行运行,顺序是非确定性的。避免有多个 hook 修改同一工具的输入。
Hooks 和权限模式
通过设置文件或插件的hooks/hooks.json 中的 hook,您无法将限制放宽到超出权限规则允许的范围,而 hook 的 "deny" 在每种权限模式下都有效:
- 对
"allow"仍然有效的限制:返回"allow"的 hook 不会绕过设置中的拒绝规则,并且在以下情况下也无法抑制提示:- 从网络路径读取
- 标记为
requiresUserInteraction的 MCP 工具 - 在该设置会传达到 Claude Code 的会话中,您的组织设置为
ask的连接器工具
PreToolUse hook 对文件编辑返回 "allow",您得到的提示、阻止或批准与没有 hook 时该编辑所得到的相同。要解除对 hook 批准的这一限制,请批准计划或切换到其他权限模式。
"deny"在每种权限模式下都有效:返回permissionDecision: "deny"的 hook 会阻止工具,即使在bypassPermissions模式或使用--dangerously-skip-permissions时也是如此。PreToolUsehook 在任何权限模式检查之前触发,在每个权限模式中,包括dontAsk。这让您能够强制执行用户无法通过更改其权限模式来绕过的策略。
如果您的机器上没有托管设置,并且您的会话未使用 Team 或 Enterprise 计划的
/login 凭据,那么 mod 可以批准被您的 PreToolUse hook 阻止的调用。改为使用 API 密钥或长期有效的 CLAUDE_CODE_OAUTH_TOKEN 运行的会话并未使用该登录,即使该登录已存储。在计划模式中,仅当此类被阻止的调用为只读(因此不是文件编辑)时,mod 才能批准它。有关 mod 的批准无法越过您的规则和 hook 的情况,请参阅使用 hook 扩展权限。Hook 未触发
Hook 已配置但从不执行。- 运行
/hooks并确认 hook 出现在正确的事件下 - 对于 skill frontmatter 中的 hook,请调用该 skill 或让 Claude 调用它,然后再次运行
/hooks。/hooks不会列出会话中尚未调用的 skill 中的 hook。如果/hooks仍未列出该 hook,请检查其他原因,例如未能解析的 frontmatter 或某项 skill hook 的限制 - 检查匹配器模式是否与工具名称完全匹配。匹配器区分大小写
- 验证你是否触发了正确的事件类型:
PreToolUse在工具执行前触发,PostToolUse在之后触发。PermissionRequesthook 在 Claude Code 即将要求你获得权限时触发;有关非交互模式情况,请参阅限制
Hook 输出中的错误
你在成绩单中看到类似”PreToolUse hook error: …”的消息。-
你的脚本意外以非零代码退出。通过管道传递示例 JSON 来手动测试它:
-
如果你看到”command not found”,使用绝对路径或
${CLAUDE_PROJECT_DIR}来引用脚本。为了完全避免 shell 引用,添加"args": []来切换到 exec 形式,它直接生成脚本而不使用 shell -
如果你看到”jq: command not found”,安装
jq或使用 Python/Node.js 进行 JSON 解析 -
如果消息包含
blocking because Git Bash wasn't found,说明您使用的是 Windows,Claude Code 未找到 Git Bash,并且失败或超时的 hook 阻止了该工具调用。有关修复方法,请参阅在没有 Git Bash 的 Windows 上失败的 hook -
如果通知显示 JSON 验证消息,你的 hook 的 stdout 解析为 JSON 但未通过架构验证。如果显示 JSON 解析消息,stdout 看起来像 JSON 对象但不是有效的 JSON。即使在退出 0 时也会发生两者。
要修复解析失败,使用 JSON 编码器(如
jq)而不是字符串连接来构建有效负载,以便值内的引号和反斜杠被转义。参考的退出代码输出部分涵盖了退出代码和 JSON 组合 -
如果脚本根本没有运行,使其可执行:
chmod +x ./my-hook.sh
/hooks 显示未配置 hooks
你编辑了设置文件但 hooks 不出现在菜单中。
- 文件编辑通常会自动拾取。如果几秒钟后它们还没有出现,文件监视器可能错过了更改:重新启动你的会话以强制重新加载。
- 验证你的 JSON 有效:不允许尾随逗号和注释
- 确认设置文件在正确的位置:
.claude/settings.json用于项目 hooks,~/.claude/settings.json用于全局 hooks - 如果菜单显示
Only hooks from managed settings run here,说明您的组织设置了allowManagedHooksOnly。您的用户、项目和本地设置文件中的 hook 不会运行,也不会列出
Stop hook 达到阻止上限
Claude 继续工作而不是停止,然后以警告结束该轮,表示 Stop hook 连续阻止了太多次。 当 Stop hook 连续阻止 8 次、且期间 Claude 没有进行任何工具调用时,Claude Code 会覆盖该 hook。您的 hook 脚本需要检查它是否已经触发过继续。从 JSON 输入中解析stop_hook_active 字段,如果为 true 则提前退出:
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 提高上限。
Hook JSON 无效果
你的 hook 打印有效的 JSON,但决策没有生效,成绩单中没有出现错误。检查哪个原因适用:- JSON 前面有额外输出:有其他内容先写入了 stdout,通常是您的 shell profile 中无条件的
echo,导致输出不再以{开头。请参阅 JSON 之前的 shell profile 输出。 - 字段位于错误的层级:将每个字段的位置与 JSON 输出格式进行比较。例如,
permissionDecision应位于hookSpecificOutput内部,而不是顶层。请参阅字段位于错误的层级。
JSON 之前的 shell profile 输出
Hook 在非交互式 shell 中运行,但 Git Bash 和某些配置(例如BASH_ENV 指向 ~/.bashrc)仍会加载您的 profile,profile 打印的任何内容都会先于 hook 的 JSON 进入 stdout:
{ 开头的输出作为纯文本读取,因此您的 JSON 会被忽略。由于 hook 以 0 退出,会话记录中也不会显示错误。要检查是否为此原因,请使用 claude --debug 启动 Claude Code,触发该 hook,然后在调试日志中搜索 Hook output does not start with {。要修复此问题,请包装 profile 中的 echo 语句,使其仅在交互式 shell 中运行:
$- 变量包含 shell 标志,i 表示交互式。Hooks 在非交互式 shell 中运行,因此 echo 被跳过。
字段位于错误的层级
当你的 hook 返回permissionDecision 或 additionalContext 在顶级而不是在 hookSpecificOutput 内部时,JSON 仍然解析,Claude Code 忽略错误放置的字段而不报告错误。要查看它忽略了哪些字段,使用 claude --debug 启动 Claude Code 并在调试日志中搜索 Hook JSON output had unrecognized keys。
检查 hook 的执行结果
按Ctrl+O 打开会话记录视图,查找 hook 的结果:
- 成功:您看不到任何内容,除非 hook 的 JSON 显示了某些内容,例如
systemMessage或 Stop hook 反馈。- 要确认 hook 已运行,请检查其效果,例如被重新格式化的文件
- 阻止错误:在大多数事件上,您会看到随阻止一起返回的消息,例如
Blocked: rm commands are not allowed。在少数事件上,例如ConfigChange和Elicitation,您不会看到消息。退出码 2 介绍了该消息的来源。 - 非阻止错误:您会看到
<hook name> hook error通知,其中包含简短说明,例如Failed with non-blocking status code:之后的 stderr 第一行,或 JSON 验证或解析消息。操作会继续进行。
claude --debug-file /tmp/claude.log 启动 Claude Code 以写入已知路径,然后在另一个终端中运行 tail -f /tmp/claude.log。如果您启动时没有使用该标志,请在会话中运行 /debug 以启用日志记录并找到日志路径。
了解更多
- Hooks 参考:完整的事件架构、JSON 输出格式、异步 hooks 和 MCP 工具 hooks
- 安全考虑:在共享或生产环境中部署 hooks 之前查看
- Bash 命令验证器示例:完整的参考实现