hook-development:给 Claude Code 装上九个拦截点

让 Claude Code 自己跑上一小时,回来你看到的是一堆已经发生的改动。写文件、跑命令、调 MCP 工具,全执行完了,你能做的只有事后翻记录。这套模式爽是爽,但控制点到底在哪,很多人答不上来。

hook 就是这个问题的答案。Anthropic 挂在 Smithery 上的 hook-development,是官方给的 hook 编写说明书,版本 0.1.0。正文不算长,却塞了 10 个现成 pattern、3 份 references 文档和 3 个校验脚本,基本把插件里挂钩子这件事讲透了。

hook-development:给 Claude Code 装上九个拦截点

我一开始以为 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 里其他字段的覆盖逻辑不一样。

hook-development:给 Claude Code 装上九个拦截点

配置格式有两套,这是最容易踩的坑。插件里放 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-development:给 Claude Code 装上九个拦截点

并行是这套设计里最被人误读的一点。文档写得很直白:所有匹配的 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-development:给 Claude Code 装上九个拦截点

有个限制必须提前知道。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,这套治理本身的成本和可靠性,目前还没有人给出量化答案。

skills资源

skill-development:一份教你造技能本身的技能说明书

2026-8-28 16:42:52

行业动态

Qoder 工程实践:Harness Engineering 指南

2026-4-3 17:00:00

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧