Hooks 深入与实战
Hooks 深入与实战
Section titled “Hooks 深入与实战”CLAUDE.md 是「贴在墙上的公约」——大家都说好,但没人盯着你执行。Hooks(钩子)则像一位铁面门卫:站在工具调用的必经之路上,规则写进他的值班手册,符合就放行,违反就拦下。公约靠自觉,门卫靠强制。
这一篇把 9 个生命周期事件、matcher 语法、阻断机制和五组实战示例一次讲透。
Hooks 的配置位置
Section titled “Hooks 的配置位置”Hooks 写在 settings.json(项目级 .claude/settings.json、用户级 ~/.claude/settings.json)的 hooks 字段下。也可以用 /hooks 命令交互式管理——加、删、看,全在命令面板里完成。
基本结构长这样:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_FILE_PATHS" } ] } ] }}每个事件下挂一组规则,每条规则有 matcher(匹配哪些工具)和 hooks(执行什么命令)。
9 大生命周期事件
Section titled “9 大生命周期事件”Hooks 挂在 9 个时间点上,覆盖一次会话从开始到结束的全程:
| 事件 | 触发时机 | 能否阻断 |
|---|---|---|
PreToolUse |
工具调用之前 | ✅ 可阻断该次调用 |
PostToolUse |
工具调用之后 | ❌(已执行完) |
UserPromptSubmit |
用户提交 prompt 之前 | ✅ 可拦截输入 |
Notification |
Claude 发送通知时 | ❌ |
Stop |
Claude 完成响应时 | ✅ 可阻止结束 |
SubagentStop |
子代理完成时 | ✅ 可阻止子代理结束 |
PreCompact |
执行 compact 之前 | ❌ |
SessionStart |
会话开始或恢复时 | ❌ |
SessionEnd |
会话结束时 | ❌ |
最常用的是 PreToolUse(拦截危险操作)和 PostToolUse(事后清理,如格式化、跑测试)。
matcher 语法
Section titled “matcher 语法”matcher 决定这条规则匹配哪些工具调用:
"matcher": "*" // 匹配所有工具"matcher": "Bash" // 只匹配 Bash"matcher": "Edit|Write" // 匹配 Edit 或 Write(管道 = 或)想拦下所有写文件的动作?Edit|Write 即可。想盯住所有 bash 命令?Bash 一个就够。
stdin 与 exit code:门卫的对话方式
Section titled “stdin 与 exit code:门卫的对话方式”Hook 命令通过 stdin 收到一段 JSON,里面带着这次调用的上下文。最关键的字段是 tool_input——比如 Bash 命令的 tool_input.command 就是即将执行的命令文本,Edit 的 tool_input 带着文件路径和改动内容。
{ "session_id": "abc123", "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/important" }}exit code 决定放行还是拦截
Section titled “exit code 决定放行还是拦截”- exit 0:放行,门卫点头。
- exit 2:阻断,并把 stderr 的内容反馈给 Claude。门卫不仅拦下,还递上一张条子说明为什么拦——Claude 读到后会调整下一步。
- 其他非零 exit code:非阻断错误,记到日志里,但不拦工具调用。
exit 2 是最有用的一种:它让规则变成「可解释的强制」——不是闷头拦下让 Claude 一头雾水,而是告诉它「这条命令动了 .env,请换个做法」。Claude 读到 stderr 的反馈后,通常会换一条路径:比如你想删 .env,它被拦下后会改成「用 git restore 撤销改动」或「先备份再删」。这种「拦下 + 解释」的反馈环,比单纯拒绝更接近一位讲道理的门卫。
一个被忽视的组合:SessionStart 与 SessionEnd
Section titled “一个被忽视的组合:SessionStart 与 SessionEnd”会话两端的钩子常被忽略,但用好了很省心。SessionStart 在新会话或 claude -c 恢复时触发,可以用来打印一段项目背景、加载当前 git 分支名;SessionEnd 在会话结束时触发,适合做收尾归档——比如把会话摘要写进项目日志,或统计本次 token 用量。它们不阻断流程,只在边界处悄悄记一笔,等于给每次会话装了「进出场打卡机」。
五组实战示例
Section titled “五组实战示例”以下示例均来自官方 hooks-guide,可复制即用。
1. 日志 hook:记录所有 bash 命令
Section titled “1. 日志 hook:记录所有 bash 命令”把 Claude 跑过的每条命令都存一份审计日志。
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.command' >> ~/.claude/bash-audit.log" } ] } ] }}从 stdin 读 JSON,用 jq 抽出命令文本,追加到日志文件。事后复盘「它到底跑了什么」一目了然。
2. 代码格式化 hook:编辑后自动跑 prettier
Section titled “2. 代码格式化 hook:编辑后自动跑 prettier”每次 Edit/Write 之后,自动格式化被改的文件。
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "prettier --write $(jq -r '.tool_input.file_path' || true)" } ] } ] }}这样无论 Claude 写出的代码缩进多乱,落盘前都会被 prettier 拉直。
3. Markdown 格式化 hook:自动补语言标签
Section titled “3. Markdown 格式化 hook:自动补语言标签”团队要求代码块必须带语言标签(```bash 而非裸 ```)。用 Python 脚本在写 Markdown 后自动补全:
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "python3 ~/.claude/scripts/format-markdown.py" } ] } ] }}format-markdown.py 读 stdin 拿到文件路径,扫描裸代码块,补上语言标签。规则交给机器执行,比在 CLAUDE.md 里反复叮嘱靠谱得多。
4. 自定义通知 hook:用 notify-send 提醒
Section titled “4. 自定义通知 hook:用 notify-send 提醒”长任务跑完,桌面弹个通知。
{ "hooks": { "Notification": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "notify-send 'Claude Code' '需要你注意一下'" } ] } ] }}macOS 可换成 osascript -e 'display notification \"需要你注意\" with title \"Claude Code\"'。
5. 文件保护 hook:阻止编辑敏感文件
Section titled “5. 文件保护 hook:阻止编辑敏感文件”防止 Claude 误改 .env、package-lock.json、.git/ 下的文件。
{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | grep -Eq '(^|/)(\\.env|package-lock.json)(/|$)|/\\.git/' && { echo '禁止编辑敏感文件,请改用其他方式'; exit 2; } || exit 0" } ] } ] }}exit 2 把「禁止编辑敏感文件」反馈给 Claude,它就知道换个思路而不是反复撞墙。这是门卫最经典的工作方式:拦下、解释、引导。
安全注意事项
Section titled “安全注意事项”Hooks 自动运行,而且和你当前用户一样有权限——能读环境变量、能跑命令、能联网。这带来三个风险:
- 凭证泄露:Hook 能读到当前 shell 的所有环境变量,包括 API key、token。
- 恶意 hook:从别处拷来的
settings.json里可能藏着一个 PreToolUse 钩子,悄悄把你代码外传。 - 供应链:你装的工具(如某个 prettier 插件)被投毒,hook 一跑就触雷。
防护建议:
- 不从不信任的来源直接导入 settings.json,导入前人工审一遍
hooks字段。 - 限制 hook 命令的权限:能不联网就别联网,能只读就别写。
- 定期审计:用
/hooks看看当前挂着哪些钩子,确认都是你想要的。
Hook 没按预期工作?三步排查:
-
看
/hooks:确认规则真的挂上了、matcher 写对没。 -
手动喂 JSON:把一段示例 stdin 喂给命令,看输出和 exit code。
Terminal window echo '{"tool_input":{"file_path":".env"}}' | <你的 hook 命令>; echo $? -
看 stderr:
exit 2的反馈内容来自 stderr,把它打到日志确认 Claude 收到了什么。如果反馈含糊,Claude 可能反复撞同一堵墙;写清「为什么拦」比单纯「拦下」重要得多。 -
临时加日志:在命令前塞一行
echo "$@" >> /tmp/hook-debug.log,把每次收到的原始 JSON 存下来,回放排查最直观。
Hooks vs CLAUDE.md
Section titled “Hooks vs CLAUDE.md”| 维度 | CLAUDE.md | Hooks |
|---|---|---|
| 性质 | 建议性(advisory) | 强制性(enforced) |
| 执行 | 模型「尽量遵守」 | 100% 执行 |
| 适合 | 风格偏好、命名约定、上下文背景 | 安全红线、格式化、审计 |
| 失效方式 | 模型忘了就破例 | 几乎不会破例 |
经验之谈:把「应该这样」写进 CLAUDE.md,把「绝不能那样」交给 Hooks。前者靠模型自觉,后者靠门卫把门。两者配合,才是既灵活又可靠的纪律体系。
Hooks 让规则从「墙上的公约」变成「门口的门卫」。下一篇看 MCP 深入,了解怎么用万能插座接通外部服务。🚀