🎯 核心主张(一句话)
CLAUDE.md 和 Skills 只能"建议"Claude 怎么做,Hooks 在系统执行层直接拦截行为——凡是必须 100% 执行的规则,写进 Hook,不要写进提示词。
📖 正文
为什么需要 Hooks:概率约束防不住深夜的手滑
书中开篇案例:工程师深夜疲惫中把 .env 推上远程仓库,API key 全部泄露,全团队花半天轮换密钥。问题不在他不知道规则,而在于此前所有机制都只是"建议"——CLAUDE.md 是规范、Skills 是指导、子智能体是委派,三者全部作用于 Claude 的认知层。语言模型是概率系统,理论上可以忽略提示词里的任何约束。
Hooks 不同:它工作在系统执行层。当 Claude 要执行 rm -rf / 时,PreToolUse Hook 不是劝它别做,而是让这次工具调用根本无法落地。软件工程术语叫策略与机制分离(Policy vs. Mechanism):CLAUDE.md 定义"应该怎么做",Hooks 保证"违反就被物理阻止"——就像文件权限从不依赖应用程序的自觉。类比:CLAUDE.md 是交通标志,Skills 是驾驶手册,Hooks 是路障和限速器。
事件类型:17 个事件,先记"能不能阻止"
事件已从早期 7 个扩到 17 个,不必背全,抓分类即可:
| 类别 | 事件 | 典型用途 |
|---|---|---|
| 会话级 | SessionStart / SessionEnd / PreCompact | 启动时经 CLAUDE_ENV_FILE 注入环境变量;压缩前备份对话 |
| 工具调用 | PreToolUse / PostToolUse / PostToolUseFailure / PermissionRequest / UserPromptSubmit | 执行前拦截;执行后反馈 lint 结果、自动格式化;编程式批准权限;给每条用户输入附加 Git 分支信息 |
| 子智能体 | SubagentStart / SubagentStop | 启动时注入团队规范;结束时读 agent_transcript_path 验收产出 |
| 完成 | Stop / Notification | 质量门控(测试不过不许停);告警转发到 Slack/钉钉 |
| 较新 | TeammateIdle / TaskCompleted / ConfigChange / WorktreeCreate / WorktreeRemove | 多智能体协作、配置审计、worktree 初始化与清理 |
最关键的维度是"能否阻止":PreToolUse、PermissionRequest、UserPromptSubmit、Stop、SubagentStop 等具备阻断力,用于控制流程;PostToolUse、Notification、SubagentStart 等只读,用于注入信息和触发副作用。时间有限就精通三个:PreToolUse(守门员)、PostToolUse(质量守卫)、Stop(完工门控)。
控制语义:exit code 分意图,JSON 传决策
command 型 Hook 从 stdin 收 JSON 上下文(tool_name、tool_input 等),从 stdout 输出 JSON 决策,用退出码表达意图:
- exit 0:成功,按 stdout 里的 JSON 执行决策
- exit 2:有意阻止,stderr 内容作为原因反馈给 Claude
- 其他:脚本自身故障,不阻断主流程——烟雾报警器坏了,不能因此封楼
JSON 决策的核心字段:permissionDecision 三个值——allow(放行)、deny(拒绝)、ask(拿不准,交人裁决);updatedInput 可以静默改参数,比如把 rm -rf tmp 悄悄改成带 --dry-run 的版本,任务继续、风险归零;additionalContext 把信息注入 Claude 上下文,是"修改→检查→反馈→修复"自动循环的载体;顶层 continue: false 是紧急制动,systemMessage 只给人看、不进模型。
Stop Hook 阻断用 decision: "block",但必须检查输入里的 stop_hook_active 标志:为 true 说明已经拦过一轮,本次必须放行——没有这个终止条件,Claude 会陷进"修复→测试失败→再修复"的死循环。
配置:6 个层级,3 层嵌套

配置分层叠加、按作用域生效。日常最重要的三个位置:.claude/settings.json(团队规则,进 Git,克隆即同步)、.claude/settings.local.json(个人覆盖,被 gitignore)、~/.claude/settings.json(跨项目个人偏好)。第四个位置是子智能体 Frontmatter 内联 Hook:只对该子智能体生效,随其启动激活、结束清理,跟着 md 文件一起分发——比在全局无差别拦截所有 Bash 命令精准得多。
结构固定为三层嵌套:事件类型 → matcher 组 → 处理器列表。matcher 是工具名匹配(Bash、Write|Edit、*);Stop 等生命周期事件忽略 matcher;Subagent 事件的 matcher 匹配的是子智能体类型名。
3 种处理器:确定性递减的阶梯

- command:跑 shell 脚本,确定性最高。正则、文件名检查这类规则判断,永远比模型判断快且可信。
- prompt:单次小模型(通常 Haiku)评估,返回
{"ok": bool, "reason": "..."}。适合需要语义理解但一步能答的场景。 - agent:起一个子智能体,可用 Read/Grep 做多轮验证(上限 50 轮)。适合"必须实际看代码才能下结论"的检查。
降级原则:能 command 不 prompt,能 prompt 不 agent。 另有异步选项:"async": true 让 Hook 后台运行不阻塞(仅 command 支持),结果下一轮传回;但异步 Hook 失去了事前拦截时机,只配做日志、通知这类事后处理,安全检查必须同步。
实战:三道防线的完整配置
事前拦截 + 事中防护 + 事后审计,就是开篇事故的解药:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "./.claude/hooks/block-dangerous.sh" }] },
{ "matcher": "Write|Edit",
"hooks": [{ "type": "command", "command": "./.claude/hooks/protect-files.sh" }] }
],
"PostToolUse": [
{ "matcher": "*",
"hooks": [{ "type": "command", "command": "./.claude/hooks/audit-log.sh" }] }
]
}
}
三个脚本的要点:block-dangerous.sh 用 jq 解析 stdin,对危险模式列表(rm -rf /、fork 炸弹、git push --force origin main、DROP DATABASE、curl | sh……)逐一匹配,命中即 deny + exit 2;protect-files.sh 按文件名(.env、credentials.json、id_rsa)、扩展名(pem/key/p12)、目录(.git/、.ssh/)三层检查写入目标;audit-log.sh 把每次工具调用记入按日切分的日志,回答"Claude 何时对什么执行了什么"。
质量侧同理:PostToolUse 挂 prettier/black 自动格式化(工具不存在就静默跳过,优雅降级),挂 eslint 把报错经 additionalContext 喂回 Claude 让它自己修;Stop 挂测试套件,不过就 block。
安全与落地注意
- stdout 只放 JSON,调试信息一律
>&2。zshrc 里无条件的 echo 会污染 stdout 导致解析失败,要用<span class="wikilink" data-target="$- == *i*" title="$- == *i*">$- == *i*</span>包住。 - 改完 settings.json 不会即时生效——配置在启动时快照,需在 /hooks 菜单确认或重启会话。
- Hook 以你的本机权限执行任意命令,来路不明的 Hook 配置等同于来路不明的可执行文件,入库前必须审查。
- Hook 是团队基础设施:上线走"三步走"——先用
matcher: "*"的审计日志观察几天真实调用模式,再据数据写针对性拦截规则,最后逐步收紧且保留日志。每条拦截必须附清晰原因,否则队友被莫名卡住只会怨声载道。
🧭 业界视角(书外补充)
- 分层选型共识(社区多方实践汇总):提示词模板 → slash command;有领域逻辑和辅助文件 → Skill;需要隔离上下文的并行工作 → 子智能体;必须 100% 执行的规则 → Hook。最常见的错误不是不会用某机制,而是层放错了——把该用 Hook 强制的行为写进系统提示词,只能概率性生效,等于把安全线交给掷骰子。
- Boris Cherny(Claude Code 创造者)的验证回路:给 Claude 一个能自查工作的手段(测试命令、lint、截图对比),产出质量约提升 2-3 倍。Hook 正是这个思路的强制版——PostToolUse 的 lint 反馈把"希望它记得跑检查"变成"每次写文件后必然跑检查",比把验证命令写在 CLAUDE.md 里靠得住一个量级。
- Cherny 的"反应式维护法"同样适用于 Hook:不要预先堆一堆想象出来的拦截规则,等真实错误发生后再补上能防住它的那一条。这与书中"先观测后管控"的三步走互相印证——规则要因真实事故而存在,不是愿望清单。
🔗 知识网络
- upstream:Claude Code 四层架构与 Agentic Loop
- downstream:ClaudeHeadless 模式与 CI-CD 集成、Claude Code 成本与安全工程
- 平行关联:CLAUDE.md 记忆系统工程(认知层建议 vs 执行层强制)、Claude Code子智能体:上下文隔离与任务委派(Frontmatter 内联 Hook)、Skills:渐进式披露的知识包