钩子跟主循环说话靠 stdout 上的一段 JSON。字段对了才生效,错了等于没返回——而且不报错。 decision 管拦不拦、reason 管拦了之后怎么办。reason 不是日志,是给模型看的指令。
上一节讲处理器怎么选,这一节讲它执行完之后怎么回话。 这一节是 Hooks 里最容易白干的一处:逻辑全对,格式错一个字段,效果为零。
一、三个字段
decision:要不要拦住这次动作。取值是 block 或者空,别的值不认识。 reason:拦的原因。它会回给模型,所以写的是给模型看的话,不是给人看的日志。 hookSpecificOutput:更细的决定,按事件不同而不同。比如工具调用前可以带权限决定。
三个字段之外的东西主循环不认。多写不报错,只是被忽略。 少写会出问题:只写 decision 不写 reason,拦是拦住了,但模型不知道为什么。
二、五份样本,两份合法
演示里五份输出:一份标准 block 带 reason、一份用了不存在的值(deny)、一份 block 但没 reason、 一份用 hookSpecificOutput、一份根本不是 JSON。 合法的两份是 A 和 D。B 的 deny 不是这个字段的取值,C 少了原因,E 压根没解析出来。 注意 D:它走的是另一条通道(hookSpecificOutput 里的权限决定),效果等价,写法不同。
两条通道并存是这套协议的一个特点:粗粒度的用 decision,细粒度的用 hookSpecificOutput。 所以写之前要先确认「我要拦的是整个动作,还是某个权限维度」。选错通道,字段写得再对也没用。
三、reason 为什么是给模型的指令
拦是拦住了,但模型收不到原因,它只知道「这次被拒了」。 接下来它会换个写法再试——而那个写法一样会被拒。两轮白跑,人还得介入。 给了原因就不一样:「命令里含 rm -rf,请改用精确路径删除」。模型会照着改,一次就过。
所以 reason 的写法该是「不行加怎么办」,不是「不行」。 这条在工程上很实用:一个钩子的好坏,一半看它拦得准不准,一半看它的 reason 写得清不清。
四、两条硬规矩
第一条:stdout 只放 JSON。前面多打一行调试信息,整段就不是 JSON 了,解析直接失败。 调试信息走 stderr。 第二条:退出码也算一种回答。 退出码零表示放行,非零表示拦下——两条通道同时存在时以哪条为准,要看具体事件,写之前先查清。 这两条合起来解释了一个现象:钩子不生效的时候,往往不是逻辑错了,是「回话的方式」错了。
所以调试钩子的第一步永远是:把它的 stdout 原样打出来看一眼,别猜。
五、一句话总结这套协议
钩子和主循环之间没有「商量」,只有「返回」。它不问你为什么,只看你返回了什么。 所以返回值的每个字段都得写对,写错不会报错,只会静默失效。 静默失效是这套体系里最难查的一类问题——它和「钩子根本没被触发」在表面上完全一样。
区分办法只有一个:在钩子开头写一行日志。有日志说明触发了,没日志说明没触发,剩下的事就是查格式。 这条办法在 5.10 节会展开成一套完整的排查顺序。
六、动手 Demo:五份钩子输出,几份能被主循环认
五份 stdout 样本:标准的、用错值的、缺 reason 的、走另一条通道的、根本不是 JSON 的。 逐个解析,看哪份能被认,以及被认的那份到底说了什么。
try:
d = json.loads(txt)
except Exception:
ok = u"无效" # 连 JSON 都不是
if d.get(u"decision") not in (u"block", u"approve", u""):
ok = u"无效" # 值不认识
实跑输出:
--------------------------------------------------------------------
五份钩子输出,几份能被主循环认
--------------------------------------------------------------------
钩子跟主循环说话靠 stdout 上的一段 JSON。格式对了才生效,格式错了等于没返回。
① 一份合法的输出长什么样
--------------------------------------------------------------------
| 字段 | 取值 | 含义 |
|---|---|---|
| decision | block 或空 | 要不要拦住这次动作 |
| reason | 字符串 | 拦的原因,会回给模型 |
| hookSpecificOutput | 对象 | 更细的决定,按事件不同而不同 |
② 五份输出逐个解析
--------------------------------------------------------------------
| 输出 | 检查 | reason | 结论 |
|---|---|---|---|
| A | 合法 | 有 | 有效 |
| B | decision 值非法 | — | 无效 |
| C | block 但缺 reason | — | 半有效 |
| D | 合法 | 无 | 有效 |
| E | 不是 JSON | — | 无效 |
五份里 2 份完全合法。
B 用了一个不存在的值(deny 不是这个字段的取值)、C 拦了但没说原因、E 根本不是 JSON。
③ 缺 reason 会怎样
--------------------------------------------------------------------
拦是拦住了,但模型收不到原因。
它只知道「这次动作被拒了」,接下来可能换个写法再试一次——而那个写法一样会被拒。
给了原因就不一样:「命令里含 rm -rf,请改用精确路径删除」。模型会照着改,一次就过。
所以 reason 不是给人看的日志,是给模型看的指令。它应该写「怎么办」,而不只是「不行」。
④ 协议的两条硬规矩
--------------------------------------------------------------------
第一条:stdout 只放 JSON。前面多打一行调试信息,整段就不是 JSON 了,解析直接失败。
调试信息要走 stderr。
第二条:退出码也算一种回答。
退出码零表示放行,非零表示拦下——两条通道同时存在的时候,以哪条为准要看具体事件,写之前先查清。
! 一句话:钩子和主循环之间没有「商量」,只有「返回」。
所以返回值的每个字段都得写对,写错不会报错,只会静默失效。
①三个字段:decision 管拦不拦、reason 管拦了之后怎么办、hookSpecificOutput 管更细的决定。 ②五份里两份完全合法。 B 用了不存在的值(deny 不是这个字段的取值)、C 拦了但没说原因、E 根本不是 JSON。 注意 D 走的是另一条通道——hookSpecificOutput 里的权限决定,效果等价但写法不同。 所以写之前要先确认「拦的是整个动作还是某个权限维度」。 ③缺 reason 会怎样:拦是拦住了,但模型不知道原因,接下来会换个写法再试——而那个写法一样会被拒, 两轮白跑。 给了原因就不一样:「命令里含 rm -rf,请改用精确路径删除」,模型会照着改,一次就过。 所以 reason 不是给人看的日志,是给模型看的指令。它该写「怎么办」,而不只是「不行」。 ④两条硬规矩:stdout 只放 JSON(调试信息走 stderr)、退出码也算一种回答。 钩子不生效的时候,往往不是逻辑错了,是「回话的方式」错了。 所以调试第一步永远是把 stdout 原样打出来看一眼。
30 秒实验
- 给 C 那份补一句 reason,重跑第 ② 步。它从「半有效」变成什么?对模型的下一轮有什么不同?
- 把 A 的 reason 改成「不行」,再想一遍:模型看到这两个字会做什么? 这和「请改用精确路径删除」差在哪?
- 在 A 的 JSON 前面加一行 print,重跑第 ② 步。它变成什么? 这说明了 stdout 那条规矩为什么是硬规矩?
常见误区
- 在 stdout 里打调试信息。整段就不是 JSON 了,解析失败,钩子等于没返回。
- 用不存在的 decision 值(deny、allow)。字段不认识,等于没返回,而且不报错。
- 只写 decision 不写 reason。拦住了但模型不知道为什么,下一轮还会撞同一堵墙。
- 把 reason 写成给人看的日志(「[钩子] 命中规则 R3」)。模型看不懂这行,改不了行为。
- 选错通道:该用 hookSpecificOutput 的用了 decision,或者反过来。字段再对也没用。
自测三问(点开对答案)
1. decision 和 hookSpecificOutput 有什么区别? —— decision 管「拦不拦这次动作」,粒度粗;hookSpecificOutput 带更细的决定,比如某个权限维度。 两条通道并存,选错等于没返回。 2. 为什么 reason 要写成「怎么办」而不是「不行」? —— 因为 reason 是回给模型的指令。只说不行,它会换个写法再撞一次;说了怎么办,它一次就能改对。 3. 为什么 stdout 里不能有别的东西? —— 因为主循环把 stdout 当协议通道,整段必须是一个 JSON。 前面多一行调试信息,解析就失败,钩子等于没返回。协议通了,下一节开始拼真的:一套完整的安全防护体系,三条钩子挂在十二个调用上。
- ☐ (本篇完,下一篇 5.6 工程实战一:安全防护体系)
相关笔记
- 处理器类型:处理器怎么选
- 安全防护:安全防护体系
- Claude Code 橙皮书 · 索引
- 🐎 Claude Code 实战 · 索引