跳转到内容

钩子 Hooks

CLAUDE.md 是「贴在墙上的规章」——员工会参考,但不保证遵守。Hooks 不一样,它是「不可贿赂的门卫」——100% 强制执行,谁也绕不过。

打个比方。你公司墙上贴着「进机房要刷卡」,有人会忘、有人会偷懒。但你装了一道闸机,不刷卡就是过不去——闸机就是 Hook。Hooks 不是建议,是物理强制

Hooks 挂在 Claude Code 工作流程的 9 个关键节点上。每个事件触发时,你配的命令就会被执行:

事件 何时触发 典型用途
PreToolUse 工具调用之前 拦截危险操作、检查前置条件
PostToolUse 工具调用之后 跑测试、格式化、检查改动
UserPromptSubmit 用户提交输入时 注入上下文、过滤敏感词
Notification Claude 发通知时 桌面提醒、声音提示
Stop 主代理结束时 收尾动作、清理
SubagentStop 子代理结束时 子代理收尾
PreCompact 压缩上下文之前 保留关键信息
SessionStart 会话开始时 加载环境、初始化
SessionEnd 会话结束时 清理、统计

最重要的两个是 PreToolUsePostToolUse,它们一前一后包住每次工具调用。

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 在工具调用完成后触发,不能阻断,但能做收尾:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npm run lint && npm run test" }
]
}
]
}
}

意思是:每次 Claude 编辑或写文件后,自动跑 lint 和 test。这保证改完代码立刻被检查——不用你记得催。

每个 Hook 可以配一个 matcher,决定它对哪些工具生效。matcher 用正则:

matcher 生效工具
Bash 只对 bash 命令
Edit|Write 对编辑和写文件
.* 所有工具
Read 只对读文件

不写 matcher 等于匹配所有工具。matcher 让你能精细控制——比如「只在我改代码后跑测试,读文件时不跑」。

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 也能分层:企业级强制安全规则、项目级团队规范、用户级个人偏好。

手写 settings.json 容易出错。用 /hooks 命令可以交互式管理钩子:

  • 查看当前所有 Hook
  • 添加新 Hook
  • 编辑或删除已有 Hook

/hooks 会引导你选事件、填 matcher、写命令,比手敲 JSON 友好。改完它会帮你写回 settings.json。

这是理解 Hooks 最关键的一点。再用一次门卫的比喻:

维度 CLAUDE.md Hooks
性质 建议性 强制性
执行 Claude 参考,可能不照做 代码级拦截,100% 执行
触发 每次会话读一次 每次工具调用都查
能阻断 不能 PreToolUse 能
适合 风格、习惯、偏好 安全红线、强制流程

一条规矩该写在哪?问自己一个问题:能不能违反。 能容忍偶尔违反的(比如「优先用函数式风格」),写 CLAUDE.md;绝对不能违反的(比如「永远不能 force push」「不能动生产配置」),写 Hook。

社区的「9 步纪律化循环」里有句话总结得好:把标准写进 CLAUDE.md,把硬规则交给 Hooks

一个团队级的 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 每次工具调用都触发,如果脚本跑得慢,会让 Claude 显得「卡」。所以:

  • Hook 脚本要,能秒级返回就秒级。
  • 别在 PostToolUse 里跑完整测试套件——用 --run 跑 vitest 的 watch 模式,或者只跑受影响的测试。
  • 耗时的检查放 PreToolUse,能在动手前就拦下,省得白干。

Hooks 是不可贿赂的门卫——9 大事件挂在工作流节点上,PreToolUse 能阻断、PostToolUse 能收尾,matcher 精细匹配,settings.json 配置。和 CLAUDE.md 的区别就一句:能违反的写墙上,不能违反的装闸机。


下一站,去 MCP 连接器 看看「万能插座」怎么把外部服务接进 Claude。🚀