钩子 Hooks
钩子 Hooks
Section titled “钩子 Hooks”CLAUDE.md 是「贴在墙上的规章」——员工会参考,但不保证遵守。Hooks 不一样,它是「不可贿赂的门卫」——100% 强制执行,谁也绕不过。
打个比方。你公司墙上贴着「进机房要刷卡」,有人会忘、有人会偷懒。但你装了一道闸机,不刷卡就是过不去——闸机就是 Hook。Hooks 不是建议,是物理强制。
9 大生命周期事件
Section titled “9 大生命周期事件”Hooks 挂在 Claude Code 工作流程的 9 个关键节点上。每个事件触发时,你配的命令就会被执行:
| 事件 | 何时触发 | 典型用途 |
|---|---|---|
| PreToolUse | 工具调用之前 | 拦截危险操作、检查前置条件 |
| PostToolUse | 工具调用之后 | 跑测试、格式化、检查改动 |
| UserPromptSubmit | 用户提交输入时 | 注入上下文、过滤敏感词 |
| Notification | Claude 发通知时 | 桌面提醒、声音提示 |
| Stop | 主代理结束时 | 收尾动作、清理 |
| SubagentStop | 子代理结束时 | 子代理收尾 |
| PreCompact | 压缩上下文之前 | 保留关键信息 |
| SessionStart | 会话开始时 | 加载环境、初始化 |
| SessionEnd | 会话结束时 | 清理、统计 |
最重要的两个是 PreToolUse 和 PostToolUse,它们一前一后包住每次工具调用。
PreToolUse:能阻断的前置门卫
Section titled “PreToolUse:能阻断的前置门卫”PreToolUse 最强大的一点:它能阻断操作。如果你的脚本返回非零退出码,Claude 的这次工具调用就会被取消。
这意味着你可以写一道「闸机」:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'git push --force'; then echo '禁止 force push'; exit 2; fi" } ] } ] }}这条规则的逻辑是:每当 Claude 要跑 Bash 命令,先检查命令里有没有 git push --force。如果有,输出提示并以退出码 2 退出——操作被拦截,Claude 看到提示就知道这条不能碰。
这就是「不可贿赂」的含义:CLAUDE.md 里写「不要 force push」可能被忽略,但 PreToolUse Hook 是代码级拦截,物理上绕不过去。
PostToolUse:后置检查
Section titled “PostToolUse:后置检查”PostToolUse 在工具调用完成后触发,不能阻断,但能做收尾:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npm run lint && npm run test" } ] } ] }}意思是:每次 Claude 编辑或写文件后,自动跑 lint 和 test。这保证改完代码立刻被检查——不用你记得催。
matcher:匹配工具
Section titled “matcher:匹配工具”每个 Hook 可以配一个 matcher,决定它对哪些工具生效。matcher 用正则:
| matcher | 生效工具 |
|---|---|
Bash |
只对 bash 命令 |
Edit|Write |
对编辑和写文件 |
.* |
所有工具 |
Read |
只对读文件 |
不写 matcher 等于匹配所有工具。matcher 让你能精细控制——比如「只在我改代码后跑测试,读文件时不跑」。
settings.json:配置位置
Section titled “settings.json:配置位置”Hooks 配在 settings.json 的 hooks 字段里。和 Memory、Commands 不同,Hooks 不是独立的 markdown 文件,而是嵌在配置里:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npm run lint && npm run test" } ] } ], "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/path/to/guard.sh" } ] } ] }}settings.json 有层级(详见 Settings),所以 Hooks 也能分层:企业级强制安全规则、项目级团队规范、用户级个人偏好。
/hooks:可视化管理
Section titled “/hooks:可视化管理”手写 settings.json 容易出错。用 /hooks 命令可以交互式管理钩子:
- 查看当前所有 Hook
- 添加新 Hook
- 编辑或删除已有 Hook
/hooks 会引导你选事件、填 matcher、写命令,比手敲 JSON 友好。改完它会帮你写回 settings.json。
与 CLAUDE.md 的本质区别
Section titled “与 CLAUDE.md 的本质区别”这是理解 Hooks 最关键的一点。再用一次门卫的比喻:
| 维度 | CLAUDE.md | Hooks |
|---|---|---|
| 性质 | 建议性 | 强制性 |
| 执行 | Claude 参考,可能不照做 | 代码级拦截,100% 执行 |
| 触发 | 每次会话读一次 | 每次工具调用都查 |
| 能阻断 | 不能 | PreToolUse 能 |
| 适合 | 风格、习惯、偏好 | 安全红线、强制流程 |
一条规矩该写在哪?问自己一个问题:能不能违反。 能容忍偶尔违反的(比如「优先用函数式风格」),写 CLAUDE.md;绝对不能违反的(比如「永远不能 force push」「不能动生产配置」),写 Hook。
社区的「9 步纪律化循环」里有句话总结得好:把标准写进 CLAUDE.md,把硬规则交给 Hooks。
实战:强制测试的完整配置
Section titled “实战:强制测试的完整配置”一个团队级的 settings.json 示例:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npm run lint -- --fix && npm run test -- --run" } ] } ], "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "scripts/block-dangerous.sh" } ] } ], "SessionStart": [ { "hooks": [ { "type": "command", "command": "echo '会话开始于' $(date)" } ] } ] }}这套配置做到:每次改文件自动 lint + test,每次跑命令先过危险操作检查,会话开始记个时间戳。全是强制的,Claude 一个都绕不过。
一个注意点:别让 Hook 拖慢你
Section titled “一个注意点:别让 Hook 拖慢你”Hook 每次工具调用都触发,如果脚本跑得慢,会让 Claude 显得「卡」。所以:
- Hook 脚本要快,能秒级返回就秒级。
- 别在 PostToolUse 里跑完整测试套件——用
--run跑 vitest 的 watch 模式,或者只跑受影响的测试。 - 耗时的检查放 PreToolUse,能在动手前就拦下,省得白干。
Hooks 是不可贿赂的门卫——9 大事件挂在工作流节点上,PreToolUse 能阻断、PostToolUse 能收尾,matcher 精细匹配,settings.json 配置。和 CLAUDE.md 的区别就一句:能违反的写墙上,不能违反的装闸机。
下一站,去 MCP 连接器 看看「万能插座」怎么把外部服务接进 Claude。🚀