Claude Code 的 CLAUDE.md、Skills、MCP,本质都是「建议」。模型可能遵守,也可能在长上下文里忘干净。Hooks 不一样:它是生命周期上的硬挂钩。事件一到,命令就跑,不经过模型判断,也不等模型「想起来」。
所以问题从来不是「Hooks 有没有用」,而是:三十多个事件里,哪些值得挂,哪些挂了只会拖慢会话、制造噪音。
下面按「值不值得挂」来拆。
先把 Hooks 说清楚
Hooks 是你写在 settings 里的处理器,在 Claude Code 生命周期的固定节点自动执行。处理器可以是 shell 命令、HTTP 请求、MCP 工具、prompt(让小模型做判断),或 agent(带工具的多轮核验)。
配置是三层嵌套:事件 → matcher(过滤何时触发)-> handler(真正执行什么)。
挂载位置决定作用域:
- ~/.claude/settings.json:本机所有项目,不进仓库
- .claude/settings.json:当前仓库,可提交给团队
- .claude/settings.local.json:当前仓库,默认 gitignore
- 企业托管策略、插件 hooks/hooks.json、Skill frontmatter:分别对应组织级、插件级、技能激活后
Claude Code 把 JSON 上下文通过 stdin 喂给 hook,hook 用 stdout 回 JSON 决策、用 stderr 给人和 debug log 看。exit 0 放行;exit 2 在可拦截事件上等于拒绝。
事件按节奏分三类:会话一次(SessionStart / SessionEnd)、一轮一次(UserPromptSubmit / Stop)、每次工具调用(PreToolUse / PostToolUse)。真正天天干活的,几乎全落在这五六个点上。
值得挂的:六条主挂钩
社区和官方示例反复出现同一批事件,不是巧合。它们对应「确定性控制」最值钱的四个场景:拦住危险动作、改完立刻收拾、给模型补活上下文、回合结束前验收。
1. PreToolUse:唯一能在动手前拦住的钩
工具参数已经生成,真正执行还没开始。matcher 按工具名过滤:Bash、Write|Edit、mcp__github__.*。
这是安全面最该挂的钩。典型用途:
- 拦 rm -rf、git push --force、DROP TABLE、写 .env、碰 /etc
- 把写入路径改写进沙箱
- 对已知安全操作直接 permissionDecision: allow,少点一次批准
它能 block。PostToolUse 拦不住------文件已经写出去了。
值得挂的判据:这件事如果漏一次就会造成不可逆损失。能用 PreToolUse 就不要用 CLAUDE.md 写「请不要删除生产数据」。
最小形态:matcher 收窄到 Bash,再用 if: “Bash(rm *)” 二次过滤,脚本里读 tool_input.command,命中危险模式就输出 permissionDecision: deny。不要对所有工具一刀切,否则每次 Read 都要付一次进程启动成本。
2. PostToolUse:改完立刻收拾,不要指望模型记得跑 formatter
工具成功返回之后触发。官方最常见的例子就是 Edit|Write 后跑 Prettier / ruff / gofmt。
值得挂的原因很具体:格式化、lint、审计日志,都是「每次改文件都该发生」的事。交给模型,它会在赶工时跳过;交给 hook,它无法跳过。
注意边界:PostToolUse 默认不能撤销已经发生的写入。你能做的是:把格式化结果写回磁盘、把 lint 警告打到 stderr 让模型下一轮看见、把变更记进审计文件。想「写坏就回滚」,逻辑要自己写,而且要极小心。
matcher 用 Write|Edit(以及你环境里实际会改文件的工具名)。命令从 stdin JSON 里用 jq 抽 tool_input.file_path,不要依赖未文档化的环境变量名------社区文章里 $CLAUDE_TOOL_INPUT_FILE_PATH 和 jq 抽取混用,版本一变就会静默失效。
3. UserPromptSubmit:在模型看到提示词之前插手
你按回车之后、模型开始推理之前。可以拦截整条提示,也可以往上下文里塞当轮才有的信息。
值得挂的用法不是「审查用户说了什么」,而是注入会过期的上下文:当前 git 分支、sprint 编号、今天的值班人、CI 是否红着。这些写进 CLAUDE.md 会过时;每轮用 hook 现查,模型拿到的是活数据。
也可以做硬规则:禁止在生产仓库提交里出现某些关键词、强制提示词带 ticket 号。exit 2 会挡住这一轮。
超时默认只有 30 秒,比多数事件短。这里只做轻量查询,不要跑测试套件。
4. SessionStart:会话开头的一次补给
新开会话、resume、clear、compact 都会触发。matcher 可以按 startup / resume / compact 区分。
值得挂:把 git status、当前分支、未完成 TODO、项目约定(用 bun 别用 npm)喂进 additionalContext。尤其是 compact 之后------摘要会丢掉细节,hook 可以把硬约定再钉回去。
不要在这里做重活。SessionStart 不能靠 exit 2 拦住会话,它是上下文通道,不是闸门。跑一次 git status、读一个本地状态文件,够了。
5. Stop:回合结束前的验收闸
模型认为自己说完了,Stop 先跑。用 prompt 型或 agent 型 hook 问一句「任务是否真的做完」;返回 {“ok”: false, “reason”: “…”},模型会拿 reason 当下一轮指令继续干。
这是「Claude 宣称完成、测试却是红的」的克星。command 型也可以:跑 npm test,失败就 exit 2。
要克制。Stop 每轮都触发。把完整 e2e 挂在 Stop 上,会话会变得又慢又贵,模型还会在测试抖动时原地打转。更稳的做法:Stop 只做快检(类型检查、单测子集),完整套件留给 CI 或你显式要求时。
prompt 型适合「只看对话文本就能判断」;要真的跑测试、读文件,用 agent 型,并设好 timeout。agent hook 目前仍标 experimental,生产路径优先 command。
6. Notification:人离开终端时唯一还值钱的钩
Claude 等你授权、或输入框闲置时触发。matcher 常见 permission_prompt 和 idle_prompt。
它不能拦截通知,只适合副作用:系统通知、Slack、响一声。桌面通知是官方入门示例,也是少数「挂上立刻改善体验、几乎零风险」的钩。
不要用它做业务逻辑。通知类 hook 的 JSON 决策字段会被丢掉。
按需再挂、默认不要挂的
PermissionRequest:自动批准「跑测试」「读仓库内文件」这类高频安全操作,能明显减少点批准的疲劳。但自动批准等于把权限策略写成代码,写错就是持续放行。先用 PreToolUse deny 黑名单,白名单批准放后面。
PreCompact:压缩上下文前备份 transcript。长会话、要做事后审计时值得挂。日常项目不必。
SubagentStop:子代理收工时验收。你真的在用自定义 subagent 流水线时才有意义。
PostToolUseFailure:工具失败后记一笔、或给模型补一句「这类失败请改用 X」。排错期有用,稳定后可摘。
其余(FileChanged、CwdChanged、Elicitation、WorktreeCreate、PreModelSwitch…)是完整表面,不是日常套餐。用得上再查官方事件表,不要为了「配置完整」全挂上。
判断「值不值得挂」的三条尺子
第一,这件事是否必须每次发生,且不能依赖模型记性。格式化、危险命令拦截、审计日志,符合;「写完记得更新 README」不符合------那是 Skills 或提示词的活。
第二,失败模式是否可接受。hook 会拖慢工具调用;挂崩了可能挡住正常操作。安全闸的失败应偏向拒绝,格式化的失败应偏向降级(命令末尾 || true,或明确处理非零退出)。
第三,作用域是否最小。能 matcher 到 Bash 就不要空 matcher;能 if: “Bash(git *)” 就不要所有 Bash。能放 .claude/settings.local.json 的个人偏好,不要提交进团队 settings。
一条经验:全局只挂 Notification 和真正跨项目的安全拦截;项目级挂 formatter、测试快检、仓库约定注入。插件和 Skill 里的 hook 只在对应能力激活时存在,适合「用这个技能才需要的检查」。
更详细的介绍参考:Hooks 实战与排错