@sairahul1: https://x.com/sairahul1/status/2069710540654645550
摘要
一份全面指南,解释Claude Code Hooks,这些是可编程的检查点,在操作前运行以强制执行规则并阻止或允许工具调用,比CLAUDE.md指令提供更可靠的控制。
查看缓存全文
缓存时间: 2026/06/24 14:26
Claude Code Hooks:最强大却无人使用的功能
CLAUDE.md 告诉 Claude 该如何表现。
Hooks 让 Claude 真正服从。
这就是大多数人忽略的差别。
你可以在 CLAUDE.md 里写“不要修改 prod.env“。
但 Claude 是否遵循,完全取决于它那一刻的判断。
Hooks 绕过了这种判断。
它们是可编程的检查点,运行在 Claude Code 的执行流程中 —— 在动作发生之前,而非之后。
这是完整的指南。
Hooks 解决的问题
Claude Code 很强大。
它能读取文件、编写代码、执行命令、调用 API。
你给它的越多,它就越有用。
但也越危险。
是什么阻止它在凌晨 2 点修改生产配置?
是什么每次都强制执行你的 lint 规则?
是什么记录每一次敏感文件读取,而不依赖 Claude 记住去做?
CLAUDE.md 是指导。
Hooks 是保证。
区别在于:Hooks 运行真实代码。它们的逻辑不依赖于模型是否理解或记住规则。而取决于你提前编写的代码。
Hook 究竟是什么
Hook 不是提示词。
也不是另一种注入上下文的方式。
它是一个可编程的控制机制,运行在 Claude Code 的执行流程内部。
当 Claude Code 即将调用工具、写入文件或执行命令时 —— Hook 在动作发生之前介入并决定:
→ 允许 → 阻止 → 询问人类确认
决定由你提前编写的代码做出。
而非由模型做出。
以下是 settings.json 中最精简的 Hook 配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'about to write a file'"
}
]
}
]
}
}
“hooks” 这个词出现了三次。每次含义不同。
让我一次性拆解这三层。
第一层 —— 事件注册表。
外层的 hooks 对象。每个键是一个事件名称 —— PreToolUse、PostToolUse、Stop。你想在哪个点干预?在此注册该事件。
第二层 —— 匹配规则。
每个事件下:一个 matcher 对象数组。matcher:“Write” 表示这一组只在 Claude 尝试调用 Write 工具时触发。没有 matcher = 匹配所有。
第三层 —— 真正的 Hook。
每个 matcher 内部:hooks 数组。这是真正的逻辑。type: command 运行 shell 脚本。type: http 调用 URL。type: mcp_tool 调用 MCP 工具。
hooks ← 整个系统的入口点 └── PreToolUse ← 事件:在任何工具调用前触发 └── matcher ← 过滤器:只匹配 “Write” 工具 └── hook ← 动作:运行此 shell 命令
Hook 事件系统
Claude Code 有 28 个 Hook 事件。
它们覆盖了 Claude 执行的每个关键点:
→ 会话开始和结束 → 工具调用前后 → 权限请求 → 文件变更 → 子代理任务 → 通知
大多数人搞错的一件事:事件是同级的,不是父子关系。
PreToolUse 和 PermissionRequest 经常接连触发。这让人以为一个导致另一个。
实际上并非如此。它们是完全独立的干预点。每个都有自己的匹配规则,互不干扰地执行。
事件分为两种类型:
主流程事件 —— 沿核心执行路径运行。这些可以阻止 Claude。PreToolUse、PermissionRequest、PostToolUse、Stop。
旁路事件 —— 观察和通知通道。它们在正确时刻触发,但不改变主流程。Notification、ConfigChange。
它们之间的关键区别:
→ 阻塞型: Hook 的结果决定 Claude 下一步做什么。主流程暂停直到 Hook 返回。 → 非阻塞型: Hook 运行,但 Claude 不等待它。适用于日志、推送通知、同步状态。
如何真正阻止 Claude
这是文档埋藏的部分。
有两种方法可以阻塞一个阻塞型事件。它们含义完全不同。
退出码 2 → 系统错误
Claude 认为出了故障。某个工具不可用、资源缺失、环境损坏。它可能试图理解错误,可能尝试变通方案。
#!/bin/bash
# 使用系统错误信号阻止
if [[ "$TOOL_INPUT" == *"prod.env"* ]]; then
echo "Environment error: target file locked" >&2
exit 2
fi
退出码 0 + JSON → 策略拒绝
Claude 将其理解为明确业务规则禁止该操作。它不会试图绕过,而是接受决定并调整行为。
#!/bin/bash
# 使用策略决定阻止(对规则而言好得多)
TOOL_INPUT=$(cat)
FILE=$(echo "$TOOL_INPUT" | jq -r '.path // ""')
if [[ "$FILE" == *"prod.env"* ]]; then
echo '{
"decision": "deny",
"reason": "Writing to prod.env is not allowed. Use staging.env instead."
}'
exit 0
fi
# 允许其他一切
echo '{"decision": "allow"}'
exit 0
JSON 方式还能做更多:
→ 附上可读的拒绝理由 → 通过 updatedInput 在 Claude 使用前修改工具输入参数 → 通过 “decision”: “ask” 请求人类确认而非自动拒绝
每个人都犯的错误:
退出码 1 什么也不做。在 Unix 惯例中表示失败。但在 Claude Code 的 Hook 系统中,退出码 1 是非阻塞的。Claude 忽略它并继续执行。
只有退出码 2 或退出码 0 + JSON 真正影响流程。
Hooks 存放在哪里
Hooks 不仅仅存在于你的个人 settings.json 中。
它们可以注册在 4 个不同的地方。每个具有不同的作用域和生命周期。
1. 设置级 Hooks —— 常驻 Hooks。
写在用户级、项目级或本地级的 settings.json 中。从 Claude Code 启动到结束一直活跃。任务之间不会清理。
~/.claude/settings.json ← 用户级,你的机器 .claude/settings.json ← 项目级,与团队共享 .claude/settings.local.json ← 本地覆盖,不提交
2. 插件 Hooks —— 随插件加载。
插件捆绑了自己的 CLAUDE.md、Skills 和 Hooks。当 Claude Code 加载插件时,其 Hooks 与主 Hooks 合并并平等运行。没有优先级区别 —— 它们一起参与。
一个硬性限制:插件子代理不能定义 Hooks。 这是故意的。子代理是受限的执行单元。允许它注册 Hooks 将使低权限角色能够修改执行流程控制。这会破坏安全模型。
3. Skill Hooks —— 限定在 Skill 作用域内。
在 Skill 被调用时注册。Skill 完成后自动清理。它们不会污染全局环境。
---
name: planning-with-files
hooks:
PreToolUse:
- matcher: "Write|Edit|Bash|Read"
hooks:
- type: command
command: "cat task_plan.md 2>/dev/null | head -30 || true"
PostToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "echo 'File updated. Update task_plan.md status.'"
Stop:
- hooks:
- type: command
command: "./scripts/final-check.sh"
---
每次此 Skill 调用 Write、Edit 或 Bash 时,它首先打印任务计划的前 30 行。每次文件写入后,它提醒 Claude 更新计划状态。Skill 结束时,运行最终检查。
4. 子代理 Hooks —— 限定在子代理作用域内。
与 Skill Hooks 相同。临时、自动清理。一个额外行为:如果在子代理的前置元数据中注册 Stop Hook,它会在运行时自动转换为 SubagentStop。因为结束的是子代理,而不是整个会话。
多个 Hooks 如何合并
在任何时刻,来自多个层的 Hooks 可能同时活跃。
当一个 Write 操作触发 PreToolUse 时,它可能匹配来自用户设置、项目配置和当前活跃 Skill 的 Hooks —— 同时发生。
有三条规则支配会发生什么。
规则 1:并行执行。
所有匹配的 Hooks 同时运行。不是串行,也不是按优先级顺序。
你的日志 Hook 和安全检查 Hook 同时启动并独立完成。Claude 等待所有 Hook 返回后才做决定。
规则 2:自动去重。
如果两个层注册了完全相同的 Hook —— 相同事件、相同匹配器、相同命令字符串 —— Claude Code 只保留一份副本并运行一次。
这在实践中很重要:你不必担心同一个脚本出现在多个配置文件中会运行两次。但反过来:如果你想让同一个脚本在两个不同条件下分别运行,确保它们的命令字符串不同。
规则 3:最严格的结果胜出。
当多个 Hooks 返回不同决定时,Claude 选择最严格的那个。
deny > ask > allow
一个 deny 就足够了。不管它来自哪个层。
用户级 Hook: allow (普通写入没问题) 项目级 Hook: ask (.env 文件需要确认) 插件 Hook: deny (prod.env 被禁止) ───────────────────────────── 最终决定: deny (一个否决就够了)
为什么这种设计有意义:在安全系统中,允许需要所有人同意。拒绝只需要一个否决。这是每个严肃安全模型的工作方式。
两个值得研究的真实 Hooks
理论很容易。让我们看看两个生产环境中的 Hooks,理解它们为什么被设计成那样。
案例 1:Superpowers 插件 —— 在会话启动时注入上下文
superpowers 插件提供了一套完整的 Skill 系统用于工程纪律:需求澄清、计划、测试驱动开发、代码审查。
但它只注册了一个 Hook。
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|clear|compact",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start"
}
]
}
]
}
}
一个事件。一个 Hook。它做什么?
它将 Superpowers skill 指令注入到每个会话中作为附加上下文。每次会话启动时,Claude 自动获得正确的初始上下文。
洞察:Hooks 不一定总是要阻止或批准。有时在正确时刻带来正确的信息就是全部工作。
案例 2:claude-code-warp 插件 —— 将 Claude 的生命周期桥接到终端
Warp 是一个终端。当你在 Warp 中使用 Claude Code 时,Warp 不知道 Claude 在做什么 —— 工作、等待、请求权限、完成。
claude-code-warp 插件通过 6 个 Hooks 解决了这个问题:
SessionStart → 向 Warp 发送初始化信息 UserPromptSubmit → 告诉 Warp:Claude 开始工作 PostToolUse → 告诉 Warp:阻塞状态已清除 Notification → 当 Claude 空闲时触发 Warp 通知 PermissionRequest → 向 Warp 发送工具名称 + 输入预览 Stop → 读取会话,提取摘要,发送完成通知
Stop Hook 是最有趣的。
它不仅仅说“完成“。它读取当前会话内容,提取最后一条用户提示和 Claude 的响应,截断到适合通知的长度,并向 Warp 的通知中心发送结构化摘要。
这一个 Hook 将 Claude Code 的内部会话记录变成了用户真正可读的完成通知。
洞察:Hooks 可以作为事件桥接 —— 将 Claude Code 的执行状态同步到本质上对 Claude 没有感知的外部系统。
一个你今天就能用的实用 Hook
保护你的生产环境文件。一个脚本。复制粘贴即可。
创建 .claude/hooks/protect-prod.sh:
#!/bin/bash
# 从标准输入读取工具输入
TOOL_INPUT=$(cat)
# 从输入 JSON 中提取文件路径
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.path // .file_path // ""')
# 检查目标是否为生产配置文件
PROTECTED_PATTERNS=("prod.env" ".env.production" "prod-secrets" "production.yaml")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo '{
"decision": "deny",
"reason": "'"$FILE_PATH"' is a protected production file. Use staging environment instead. If you genuinely need to edit production config, do it manually."
}'
exit 0
fi
done
# 不是受保护的文件 — 允许
echo '{"decision": "allow"}'
exit 0
在 .claude/settings.json 中注册:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/protect-prod.sh"
}
]
}
]
}
}
使其可执行:
chmod +x .claude/hooks/protect-prod.sh
现在每个针对生产配置的 Write 和 Edit 都会被阻止 —— 并附上明确理由 —— 在 Claude 触及文件之前。不是因为 Claude 记住了规则,而是因为代码强制执行。
让 Hooks 变得直观的心智模型
把 Claude Code 的执行流程想象成一条管道。
没有 Hooks:Claude 运行整条管道。CLAUDE.md 引导它。Skills 组织它。但执行本身是不间断的。
有 Hooks:你在管道中的任意点插入检查点。每个检查点运行真实代码。它可以检查 Claude 即将做什么,决定是否允许,甚至可以在 Claude 使用前修改输入。
这个系统有三层协同工作:
→ CLAUDE.md —— 告诉 Claude 如何理解项目 → Skills —— 将 Claude 组织成可靠的工作流 → Hooks —— 在关键执行点守卫边界
没有任何一层能替代其他层。
没有 Hooks 的 CLAUDE.md:良好的指导,不一致的强制。 没有 CLAUDE.md 的 Hooks:对模型不理解的规则的严格强制。 两者结合:每个层面都可靠的行为。
在你开始构建 Hooks 之前有一个警告。
Hooks 运行真实代码。效果是即时的,有时不可逆。
一个具有错误退出码的脚本可能意外中断流程。
一个 Stop Hook 如果退出逻辑处理不当,可能将会话困在循环中。
像测试生产代码一样测试每个 Hook。
处理边缘情况。处理错误路径。显式记录失败。
Hooks 的力量在于它们不能被忽略。
同样的属性使得 Hooks 中的错误代价高昂。
5 件要记住的事
→ 1. Hooks 不是提示词。它们是代码。无论模型处于什么状态都会运行。
→ 2. 退出码 1 什么也不做。系统错误用退出码 2。策略决定用退出码 0 + JSON。
→ 3. 事件是同级的。PreToolUse 和 PermissionRequest 独立触发。一个不会导致另一个。
→ 4. 最严格的结果胜出。来自任何层的一个 deny 就阻止操作。Allow 需要所有人同意。
→ 5. Skill 和子代理 Hooks 是临时的。它们在调用时注册,在完成时清理。它们不会污染全局状态。
如果这有用:
→ 转发与每个使用 Claude Code 构建的开发者分享 → 关注 @sairahul1 获取更多类似深入内容 → 收藏本文
我写关于 AI、产品构建以及无需你参与也能工作的系统的文章。
相似文章
luongnv89/claude-howto
一份全面、结构化的指南,帮助掌握 Claude Code 的功能,包括斜杠命令、钩子、技能、MCP 服务器和子代理,配有可视化教程、复制粘贴模板,以及从新手到高级用户的分阶段学习路径。
@humzaakhalid: 大多数开发者都在错误地使用Claude Code(100%)Claude Code在90秒内就能修复。具体方法如下:1. 开始…
一份关于如何有效使用Claude Code的详细指南,包括设置、CLAUDE.md配置、四层架构、钩子以及日常工作流程模式。
@sairahul1: 我真心不明白为什么大家还没用这个。有一个 Claude Code 功能:→ 运行你的测试…
Sai Rahul 重点介绍了 Claude Code 的 Hooks 功能,该功能可在每次编辑后自动运行测试,阻止破坏性的 bash 命令,记录花费,发送 Slack 提醒,并自动重写错误输出。
@tom_doerr: Claude Code 技能、钩子和代理的实用指南 https://github.com/wesammustafa/Claude-Code-Everything-You-Nee…
一份全面的 Claude Code 实用指南,涵盖设置、技能、钩子、MCP、代理团队以及面向开发者的提示工程。
@akshay_pachaar: Claude Code 不是一款编码工具。(它是一个可编程的开发环境)工程师打开它,输入提示词,然后让它编…
Claude Code 被定位为可编程的开发环境,而非简单的编码工具,具备12项功能,包括持久记忆(CLAUDE.md)、行为规则、可复用技能、事件钩子、斜杠命令、插件、MCP 连接、计划模式、权限、子代理、语音模式和回滚检查点。