Claude Code 实践课程 · Lesson 03

Hooks

prompt 管倾向,hook 管纪律

01

为什么需要 hook

有些规则你写在 CLAUDE.md 里:「每次改完代码跑 lint」。模型大多数时候会照做——但「大多数时候」对纪律性规则来说就是不及格。Hook 是另一条路:在生命周期的固定点位挂一段 shell 命令,机器保证执行,不经过模型判断

Prompt(CLAUDE.md)
模型理解后执行,有概率被忽略
擅长模糊指令:「优先用函数式风格」
占注意力预算
适合:倾向、风格、判断类规则
Hook(settings.json)
确定性执行,100% 触发
只能执行确定逻辑:跑命令、查路径、改输出
不占注意力预算
适合:必须发生 / 必须禁止的纪律

判断口诀:这条规则被违反一次要不要紧?不要紧 → CLAUDE.md;要紧 → hook。

02

挂载点:一个 session 的生命周期

橙色节点是可以挂 hook 的事件。注意中间那圈虚线框——那就是上节课说的 agent loop 内环,每调一次工具转一圈:

SessionStartstdout 注入上下文
会话启动。适合注入动态背景:当前分支、未提交改动、今天的日程。
UserPromptSubmitstdout 注入上下文可拦截
你每次发消息时。适合自动补充上下文(识别工单号拉详情),或拦截敏感输入。
模型思考,决定调用工具…
AGENT LOOP 内环 · 每次工具调用转一圈
PreToolUseexit 2 = 拦截调用
工具执行。唯一能阻止动作发生的点位:保护文件、拦危险命令。
工具执行(Write / Bash / …)
PostToolUsestderr 反馈给模型
工具成功。适合旁路动作:格式化、审计日志、把 lint 报错喂回模型。
Stop / SubagentStop可强制继续
Claude 准备结束回合时。适合完工检查:测试跑了吗?TODO 清了吗?没达标就退回去继续干。
SessionEnd / PreCompact / Notification
会话结束清理、上下文压缩前备份、CC 等确认时发系统通知。
03

练习:选对挂载点

8 个真实需求,为每个选出正确的事件。这是写 hook 前最重要的决策:

hook 需求清单 已选 0 / 8 · 答对 0
04

退出码协议:hook 怎么说话

Hook 脚本通过退出码和标准流与 CC 对话:

exit 0
一切正常
不干预。SessionStart / UserPromptSubmit 的 stdout 会注入上下文——这是「喂信息」的通道。
exit 2
阻断
PreToolUse:拦下这次调用。Stop:不让结束。stderr 会喂回给模型,告诉它为什么——所以报错信息要写给模型看,可操作。
其它
非阻塞错误
hook 自己出错了,CC 记录但不影响流程。复杂场景还可以输出 JSON 做精细控制(decision / reason)。
05

现场实验实录

本课在讨论文件夹真实装了两个 hook(脚本见 starter-kit/hooks/):protect.py(PreToolUse,拦截写 protected/)和 log_edits.py(PostToolUse,审计日志)。用 headless 子进程验证——以下是 2026-06-10 的真实输出,未经编辑:

测试 1 · 写 protected/ 应被拦截
$ claude -p '请在 protected/secret.md 写入 hello…' --allowedTools Write 我遇到了以下错误: hook 拦截:protected/ 目录是只读的,禁止写入。 # PreToolUse exit 2 → 调用被拦,stderr 原文喂回给了模型
测试 2 · 正常写入应放行并留痕
$ claude -p '请创建 scratch/ok.md,内容:hooks work' --allowedTools Write Done! 文件 scratch/ok.md 已创建 $ cat .claude/edit-log.txt 2026-06-10T10:41:42 Write /Users/silas/讨论/scratch/ok.md # PostToolUse exit 0 → 旁路记录,模型甚至不知道日志的存在

注意实验方法本身:hook 配置在 session 启动时快照(防运行中被偷改),所以当前会话里改 hook 不会立即生效——用 headless 子进程测试是最干净的验证方式。

06

坑与安全

hook 以你的完整权限执行
不经过权限系统,不被沙箱。装任何来源不明的 hook 前先读脚本——这也是为什么写入 hooks 配置会被分类器严格把关。
改完配置不生效
启动时快照。重启 session,或用 /hooks 菜单审查生效。
matcher 是正则
Write|Edit 匹配两个工具;空 matcher 匹配所有工具——慎用,每次调用都跑一遍脚本。
默认 60 秒超时
hook 是同步的,太慢会拖住整个回合。重活(全量测试)别放 hook 里,放 Stop 检查或让模型自己跑。
stderr 写给谁很重要
exit 2 的 stderr 是喂给模型的。写「禁止」不够,要写「该怎么办」:「protected/ 只读,如需修改请让用户确认」。
07

到新项目的验证清单

找出项目里「违反一次就要紧」的规则(CLAUDE.md 里那些写着「必须」的行),逐条评估搬进 hook。
拷 starter-kit/hooks/ 改造:把 protect.py 的路径换成项目真实的禁区(migration 历史、生成文件目录)。
装一个 PostToolUse 格式化 hook(改完文件自动 prettier/ruff),干一天活验证再也没出现过格式问题。
用 headless 子进程测试每个 hook:故意触发禁区,确认拦截信息出现;正常操作,确认不误伤。

勾选状态存在浏览器 localStorage 里。

本课带走一句话
这条规则被违反一次要不要紧?不要紧写 CLAUDE.md,要紧写 hook。