让 Claude Code 自己跑上一小时,回来你看到的是一堆已经发生的改动。写文件、跑命令、调 MCP 工具,全执行完了,你能做的只有事后翻记录。这套模式爽是爽,但控制点到底在哪,很多人答不上来。
hook 就是这个问题的答案。Anthropic 挂在 Smithery 上的 hook-development,是官方给的 hook 编写说明书,版本 0.1.0。正文不算长,却塞了 10 个现成 pattern、3 份 references 文档和 3 个校验脚本,基本把插件里挂钩子这件事讲透了。

我一开始以为 hook 无非是事件触发的 bash 脚本,跟 git hook 一个路数。翻完才发现问题没那么简单,这份文档把 prompt-based hook 摆在了推荐位,也就是让模型在工具执行前用自然语言判断该不该放行。拦截层里塞了一个会思考的裁判,这跟 git hook 已经不是同一个物种。
更准确地说,它给的是一套运行时治理框架。九个事件点串起来,刚好覆盖从会话打开到关闭的完整生命周期,而不只是工具调用那一下。
它真正要补的是 agent 自主性和可控性之间的缺口。下面我按架构、链路、落地三条线拆一遍,顺带说说我觉得哪些地方还不太放心。
架构解析
hook 系统的骨架是九个事件点,分布在会话的不同阶段,每个点都有自己的输入输出契约:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
| PreToolUse | 工具执行前 | 校验、改写入参、直接拒绝 |
| PostToolUse | 工具完成后 | 反馈、质量检查、记录日志 |
| UserPromptSubmit | 用户提交提示词 | 注入上下文、拦截不当请求 |
| Stop | 主 agent 想停时 | 完成度验收 |
| SubagentStop | 子 agent 想停时 | 子任务验收 |
| SessionStart | 会话开始 | 加载项目上下文 |
| SessionEnd | 会话结束 | 清理与状态留存 |
| PreCompact | 上下文压缩前 | 保住关键信息 |
| Notification | 发出通知时 | 审计与外发 |
九个事件分两类能力。PreToolUse 是唯一能改写工具输入的,其余基本只能观察、反馈或阻断。Stop 和 SubagentStop 则把”活干完没有”这件事从模型的自我感觉变成了外部验收,这是两个我认为价值被低估的点。
matcher 的写法是正则,很多人会忽略这一点。mcp__.*__delete.* 能一次性兜住所有 MCP 删除工具,比逐个列举稳得多。它大小写敏感,写成 write 匹配不到 Write。还有个行为差异值得记:插件自带的 hook 和用户自己的 hook 是合并执行,不是覆盖,跟 settings 里其他字段的覆盖逻辑不一样。

配置格式有两套,这是最容易踩的坑。插件里放 hooks/hooks.json,外面必须套一层 {"hooks": {...}};用户自己的 .claude/settings.json 则是事件直接写在顶层,没有 wrapper,也不认 description 字段。两套格式长得像,写错了不会报明白的错,只会让 hook 静默不生效。
{
"description": "代码质量校验钩子",
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate.sh"
}
]
}
]
}
}
路径写法值得单独说一句。文档反复强调所有脚本引用都要用 ${CLAUDE_PLUGIN_ROOT},别写绝对路径。插件是分发出去的,你机器上的目录到别人那儿根本不存在。运行时还会注入 $CLAUDE_PROJECT_DIR、$CLAUDE_ENV_FILE、$CLAUDE_CODE_REMOTE 这几个变量,可移植性基本靠它们撑着。
工作流分析
一次 PreToolUse 拦截的完整链路,比大多数人想象的要绕。Claude 准备调 Write,先按 matcher 匹配到一组 hook,组内所有 hook 并行跑,各自的 JSON 输出汇总成一条最终决策,之后才决定这个工具到底执不执行。

并行是这套设计里最被人误读的一点。文档写得很直白:所有匹配的 hook 同时执行,互不可见彼此输出,顺序不确定。这意味着你不能写 hook A 产出中间结果、hook B 接着消费,那种串联依赖在并行模型下必然偶发失败,每个 hook 都得是自洽的独立单元。
我原来以为并行只是为了省时间,后来看输出契约才反应过来,它还顺手消灭了靠执行顺序编排逻辑的可能。这算一种强制解耦,代价是想做条件链就只能把所有判断塞进单个 hook 内部,复杂度往里挪了。
还有一个容易被忽略的窗口,SessionStart 的 CLAUDE_ENV_FILE。九个事件里只有它能把环境变量写出去,让后续所有命令继承。项目类型探测、依赖版本识别这类一次性探测,都得挤在这个时间点做完。错过这个窗口,后面每次工具调用都得重新算一遍,属于典型的省小钱花大钱。
输出契约分三层,命令 hook 和 prompt hook 各走各的路。命令 hook 靠退出码说话,规则很朴素:
-
0:放行,stdout 进 transcript -
2:阻断,stderr 回灌给 Claude 当错误处理 -
其他码:非阻断错误,记录但不拦
prompt hook 走 JSON。PreToolUse 用 permissionDecision 给 allow、deny 或 ask,还能靠 updatedInput 直接改写工具入参;Stop 和 SubagentStop 则用 decision 给 approve 或 block,并附一条 reason 说明为什么不放行。
超时默认值也得记牢:命令 hook 60 秒,prompt hook 30 秒。别小看这两个数字,超时的后果不是报错,而是被当作没拦住、直接放行。模型响应慢的时候拦截会静默失效,放在安全场景里相当要命。
使用场景
文档给了 10 个 pattern,覆盖从安全校验到通知日志的完整光谱。真正高频的是这四类:
-
写文件前拦路径穿越、 /etc系统目录、.env等敏感文件 -
Stop 之前强制要求跑过测试,改了代码没测就 block 回去 -
SessionStart 探测项目类型,把结果写进 CLAUDE_ENV_FILE -
MCP 删除类操作( mcp__.*__delete.*)执行前做二次确认
前两类基本任何项目都能直接抄。第三类有点讲究,它靠 CLAUDE_ENV_FILE 把 PROJECT_TYPE=nodejs 这类变量持久化到整个会话,后面所有 hook 和命令都能读到,等于给了插件一层跨调用的状态。
{
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "prompt",
"prompt": "检查 $TOOL_INPUT.file_path:1) 不在 /etc 等系统目录 2) 不是 .env 或凭据文件 3) 不含 .. 路径穿越。返回 approve 或 deny。"
}
]
}
]
}
落地流程不长,六步走完就能上线:

有个限制必须提前知道。hook 在会话启动时加载,改完 hooks/hooks.json 或脚本不会热更新,必须退出 Claude Code 重开。我第一次读到这条时愣了一下,在一个需要反复调提示词的场景里,这个约束会明显拉长调试周期。文档给的解法是用 claude --debug 看注册日志,再用 scripts/test-hook.sh 拿样例输入直接喂脚本,别在会话里一遍遍试。
还有个挺巧的设计叫临时激活 hook。脚本开头先检查标志文件在不在,不存在就直接 exit 0,存在才跑真正的校验逻辑。想开启就在项目根目录 touch .enable-security-scan,关掉就删掉文件。性能重的检查按需开,比常驻划算得多。
洞察与反思
这份文档最值得琢磨的判断,是把 prompt-based hook 设成默认推荐。命令 hook 快且确定,但只能做正则级别的机械校验;prompt hook 慢、贵、有概率抖动,却能读懂语义。让你在一条 rm -rf 前面做判断,你要的是”这条命令会不会删掉整个项目”这种理解,不是一个关键词黑名单。
Stop hook 是另一个被低估的设计。agent 说”我做完了”,以前你只能信它。现在可以在它打算停的瞬间检查一遍:改过代码吗,跑过测试吗,构建通过了吗,没做就 block 回去。这等于把完成标准从模型的自觉变成了外部强制,是 agent 从玩具走向生产的关键一步。
不放心的地方也不少。prompt hook 的 30 秒默认超时在高负载时会变成隐性放行,拦截失败是静默的,你甚至不知道自己没被拦住。每次工具调用都可能触发若干 hook,累积延迟和 token 消耗没人帮你算。更现实的是,判断权交给了模型,模型本身也会犯错,你只是把风险从”agent 乱来”换成了”裁判漏判”,不是消除了它。
横向比一下定位会更清楚。Cursor 的 Rules 是静态注入,在生成前告诉模型该怎么做;hook 是运行时拦截,在动作发生前物理阻断;CI 的 gate 是事后卡点,Stop hook 是事中卡点。三者解决的是同一个问题在不同时间切面的版本,hook 补上的恰好是执行前一毫秒这个此前没人管的窗口。
顺带说个生态层面的观察。Smithery 页面上这个技能的 installs 显示 36,旁边另一个计数是 65489,两个数字差了三个数量级。前者的口径明确,后者页面没标注含义,从量级看更像注册表侧的累计拉取或曝光。真要解读的话,只能说看的人远多于装的人,这大概是当前插件生态的真实温度。
资源地址
| 资源 | 地址 |
|---|---|
| Smithery 技能页 | https://smithery.ai/skills/anthropics/hook-development |
| 源码位置 | anthropics/claude-code 仓库 plugins/plugin-dev/skills/hook-development/ |
| 官方文档 | https://docs.claude.com/en/docs/claude-code/hooks |
总结
hook-development 的价值不在那几段 JSON 示例,而在它把 agent 的控制面做成了可声明、可分发、可组合的东西。九个事件点加上并行执行模型,足够搭出一条从会话开始到结束的完整治理链路。
它不适合只想让 agent 少犯点错的人。如果你只是想约束几条规定,写进 CLAUDE.md 更轻。但凡要把 agent 放进有真实资产的环境里跑,这套拦截机制就不是可选项。
留个开放问题。判断权交给模型之后,谁来审裁判?九个事件并行、每次都可能触发若干 prompt hook,这套治理本身的成本和可靠性,目前还没有人给出量化答案。

