写给小白的 Claude Code 进阶指南 - Hooks

上周我在 CLAUDE.md 里写了一条规则:”每次修改代码后跑一下 TypeScript 类型检查”。
Claude 看到了。它确实会做。大部分时候。
但有一次对话拉得很长,上下文被压缩了一轮,它就悄悄跳过了这步。我没注意到,代码带着类型错误被提交了。第二天上线,炸了。
CLAUDE.md 里的规则是”建议”。Claude 会尽量遵守,但它终究是一个概率模型——它不保证每次都执行。
Hooks 不一样。Hooks 是确定性的。它每次都执行。没有例外。
一位开发者在自己的实践文章中写得很精确:”概率性指令与确定性执行的区别——这正是 Hooks 被严重低估的原因。”
Hooks 到底是什么?
一句话:Hooks 是绑定在 Claude Code 生命周期事件上的自动化脚本。
你可以把它理解为 Git Hooks 的 AI 版本。Git Hooks 在你 commit、push 的时候自动触发。Claude Code Hooks 在 Claude 读文件、写文件、跑命令、停止工作的时候自动触发。
每个 Hook 有三个要素:
- 事件(Event):什么时候触发?比如”Claude 准备使用某个工具之前”
- 匹配器(Matcher):针对哪些工具?比如”只针对 Bash 命令”
- 处理器(Handler):触发后做什么?比如”跑一个 Shell 脚本检查危险命令”
配置写在 JSON 文件里,长这样:
1 | { |
看不懂没关系。后面一步步拆解。
关键概念:22 个生命周期事件
Claude Code 的整个工作流程被拆成了 22 个事件节点。你可以在任何一个节点上挂载 Hook。
最常用的五个:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
| PreToolUse | Claude 使用工具之前 | 拦截危险命令、保护文件 |
| PostToolUse | Claude 使用工具之后 | 自动格式化、跑类型检查 |
| Stop | Claude 停止工作时 | 质量关卡、跑测试 |
| Notification | Claude 发出通知时 | 桌面提醒、发消息到 Slack |
| ConfigChange | 配置文件被修改时 | 审计追踪、阻止未授权修改 |
其中 PreToolUse 是最强大的。因为它能在 Claude 执行操作之前拦截——还没跑就被挡住了。这是你的安全层。
其他事件还包括:
SessionStart/SessionEnd:会话开始和结束UserPromptSubmit:你提交 Prompt 之后、Claude 处理之前PreCompact/PostCompact:上下文压缩前后SubagentStart/SubagentStop:子智能体启动和完成InstructionsLoaded:CLAUDE.md 或规则文件被加载时WorktreeCreate/WorktreeRemove:Worktree 创建和移除TaskCompleted:任务被标记完成时Elicitation/ElicitationResult:MCP 服务器请求用户输入时
不需要全记住,用到再查。
四种 Handler 类型
Claude Code 支持四种类型的 Hook 处理器。这是它比 Cursor、Copilot 等工具更强大的原因之一。
1. Command(Shell 脚本)
最常用的类型。写一个 Shell 脚本,Hook 触发时自动执行。
脚本通过退出码(exit code)告诉 Claude 结果:
exit 0:没问题,继续exit 2:有问题,阻止操作,并把 stderr 发给 Claude- 其他值:非阻塞性警告
1 |
|
2. Prompt(单轮 LLM 评估)
不用写脚本,直接让一个 LLM 来判断。适合需要”理解力”而不是简单模式匹配的场景。
1 | { |
3. Agent(多轮子智能体)
最强大的类型。一个有工具权限的子智能体,能读文件、搜索代码、多轮推理。
1 | { |
4. HTTP(远程服务调用)
不需要本地脚本,直接把事件数据 POST 到一个 URL。适合团队共享的中心化校验服务,或者跟内部平台集成。
1 | { |
注意两点:HTTP Hook 通过响应体返回 JSON 结果,格式和 Command Hook 一样;非 2xx 响应不会阻塞执行,只会产生一个非阻塞性错误。
选择建议: 永远从 Command 开始。需要”判断力”时升级到 Prompt;需要”调查能力”时用 Agent;需要跟远程服务集成时用 HTTP。Prompt 和 Agent 会消耗额外的 Token,HTTP 需要你维护一个服务端。
在哪里配置?
三个位置,优先级从高到低:
| 位置 | 作用范围 | 是否进 Git |
|---|---|---|
.claude/settings.json |
当前项目 | 是(团队共享) |
.claude/settings.local.json |
当前项目 | 否(个人配置) |
~/.claude/settings.json |
所有项目 | 否(全局配置) |
团队规范的 Hook 放项目配置,个人习惯的 Hook 放全局配置。
你也可以在 Claude Code 里输入 /hooks 来可视化管理,不用手写 JSON。
七个最实用的 Hook
从实用度排序,这七个 Hook 几乎适用于所有项目。
1. 桌面通知:Claude 干完活提醒你
如果你经常让 Claude 在后台跑任务,自己去看别的东西,这个 Hook 必装。
1 | { |
Linux 用户把 osascript 换成 notify-send "Claude Code" "Claude 需要你的注意"。
2. 拦截危险命令
这应该是 Claude Code 的默认功能,但它不是。你需要自己加:
1 |
|
配置:
1 | { |
注意:用的是 PreToolUse,不是 PostToolUse。我们要在命令执行之前拦截,不是执行完了才发现。
3. 自动格式化
每次 Claude 编辑文件后,自动跑 Prettier:
1 | { |
Claude Code 的创造者 Boris Cherny 自己就用这个设置。有一个细节要注意:如果格式化工具改了文件,Claude 每次都会收到文件变更的提醒,会吃上下文。更聪明的做法是改为在 Stop 事件时统一格式化,而不是每次编辑后都跑。
4. 类型检查
TypeScript 项目必备。每次 Claude 编辑 .ts 或 .tsx 文件后,自动跑类型检查:
1 |
|
这个 Hook 用 exit 2 阻塞。类型检查不通过,Claude 会收到错误信息,然后自动修复再继续。就像一个会自己检查编译的队友,而不是写完代码直接甩给你。
5. 保护关键文件
有些文件绝对不能让 AI 碰——.env、锁文件、迁移文件:
1 |
|
6. 上下文压缩后自动恢复关键信息
Claude 的上下文窗口满了会自动压缩(Compaction),压缩后可能丢失一些重要的项目约定。用 SessionStart 事件配合 compact 匹配器,每次压缩后自动注入关键信息:
1 | { |
echo 输出的内容会直接注入 Claude 的上下文。你也可以换成动态命令,比如 git log --oneline -5 来显示最近的提交。
7. 审计配置变更
团队协作中,配置文件被意外修改可能引发大问题。ConfigChange 事件能帮你追踪每一次变更:
1 | { |
每次配置变更都会被记录到日志文件。你甚至可以用 exit 2 阻止未授权的修改生效。
一个容易踩的坑:Stop Hook 死循环
这是新手最常犯的错误。
你写了一个 Stop Hook,在 Claude 停止时检查测试是否通过。测试没通过,Hook 返回 exit 2,阻止 Claude 停止。Claude 修复后试图再次停止,Hook 又触发,又检查——如果测试还是不通过,就无限循环了。
解决办法:在脚本开头检查 stop_hook_active 字段:
1 | INPUT=$(cat) |
这是 Stop Hook 的第一条规则。每个 Stop Hook 都必须有这个检查。
Hooks vs CLAUDE.md vs Skills:什么时候用什么?
这三者经常被搞混。一个社区的总结非常清晰:
| 机制 | 类比 | 本质 | 保证执行? |
|---|---|---|---|
| CLAUDE.md | 房子的规矩 | 指导性规则 | 不保证 |
| Skills | 按需启用的流程手册 | 可复用工作流 | 不保证 |
| Hooks | 房子的传感器和报警器 | 确定性自动化 | 保证 |
一个实用的判断标准:
- “最好这样做” → 写进 CLAUDE.md
- “这个流程经常重复” → 做成 Skill
- “这件事必须每次都发生” → 用 Hook
安全相关的规则,永远用 Hook。因为安全不能是”建议”,必须是”强制”。
Matcher 的进阶用法
前面的例子里 Matcher 都比较简单,比如 "Bash" 或 "Edit|Write"。但 Matcher 实际上支持正则表达式,可以玩出更多花样。
匹配 MCP 工具:MCP 工具名遵循 mcp__<服务器>__<工具> 的命名规则。你可以精确匹配:
1 | { |
这会匹配 memory 服务器的所有工具调用。mcp__.*__write.* 则匹配任何服务器的写操作。
另外,不是所有事件都支持 Matcher。UserPromptSubmit、Stop、TaskCompleted 等事件没有 Matcher 过滤,每次都会触发。加了 Matcher 也会被忽略。
一个安全细节
Claude Code 在会话启动时会对你的 Hook 配置做一次快照,整个会话期间使用这份快照。会话中途修改 Hook 不会生效。 这意味着没有人——包括 AI 自己——能在运行时篡改 Hook。
这个设计,是让 Claude Code 可以无人值守运行的前提。
写在最后
Hooks 是 Claude Code 最被低估的功能。它不像 Plan Mode 那样直观,不像 Skills 那样容易上手,但它解决了一个最根本的问题:如何让 AI 可靠地执行规则?
我自己的经验是:从两个 Hook 开始——自动格式化和危险命令拦截。这两个能防住最常见的问题,零维护成本。
等用熟了,再加类型检查、测试关卡、桌面通知。
回到开头那次上线事故——如果当时装了 PostToolUse 的类型检查 Hook,那个错误根本不会进代码库。CLAUDE.md 告诉 AI 应该怎么做,Hooks 确保 AI 必须怎么做。两者配合,才是完整的 AI 开发工作流。