
wuguanganandClaude Opus 5
fe3febbfd2
feat: Stop 拦截注入 —— 收工时拿清单逼它逐条报证据
Stop 从「非阻塞、输出被忽略」改成阻塞式,有待验收项时输出
{"decision":"block","reason":<清单>} 让 Claude 不停下来,先逐条核对。
下一轮的 lastAssistantMessage 里的自查 JSON 被收回来,把 open 推到 claimed。
Stop 的超时阶梯和审批那条**完全分开**,方向也相反:
3s HookCoordinator / 5s hubctl / 10s settings.json,超时一律 = 不拦。
审批超时 = 拒绝(安全刹车);这里超时 = 放行。写反的话 Hub 一出问题
所有会话就再也收不了工,比"少提醒一次验收"严重得多。实测 hook 本身
6-11ms,Hub 在不在都一样。
查出来三个真问题,都不是靠读代码发现的:
1. 服务端从不给 stop 回写应答。我改了 hubctl 让它等,却没动
HubSocketServer 里写死的 guard kind == .preToolUse —— 拦截一次都没生效
过,而且不报错、不超时、10ms 就返回,表现得和"清单里没有待办"
一模一样。端到端测了三轮才找到。修法不是补一个 if,是加
Kind.expectsDecision 让两边共用同一个判断。
这和上次「grep activeScreen() 漏了一个调用点」是同一类错误:
改了协议的一端,假设另一端会跟上。
2. SIGPIPE 会杀掉 Hub。服务端开始给 stop 回写之后,遇到不等应答就关连接
的客户端(旧版 hubctl、被 Ctrl-C 的 hook),那次 write 直接终止进程 ——
不是报错,是进程没了。测试崩在 signal 13 上才暴露。
先加了 per-socket 的 SO_NOSIGPIPE(看起来更讲究),结果没生效,
照样崩;改成进程级 signal(SIGPIPE, SIG_IGN) 才好。
更讲究的写法如果不生效,就只是更讲究的 bug。
3. stop 事件不能去重。缓存决策只有在决策是事件的纯函数时才成立,
而 stop 的结论取决于会变的上膛状态:同样内容的两条 stop,
一条在用户说话前、一条在之后,结论截然相反。实测被这个坑到过。
4. {"done":1} 被判成做完了。JSONSerialization 把数字 1 桥接成 NSNumber,
as? Bool 对它成立 —— 模型只要把 done 写成数字就能绕过"含糊按没做算"。
改用 CFBooleanGetTypeID 严格判定。
端到端实测(真实 hubctl 二进制):未上膛不拦、上膛后拦下 13 条要点、
连着两次收工都放行、Hub 存活、自查回答(带散文和围栏)正确回收成
claimed 且 hasProof 仍为 false。
346 个测试。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 18:10:35 +08:00
..
2026-08-04 17:19:39 +08:00
2026-08-07 18:10:35 +08:00
2026-08-07 18:10:35 +08:00
2026-07-29 19:20:12 +08:00
2026-08-07 18:10:35 +08:00
2026-08-07 18:10:35 +08:00