What problem does this solve?
我们依赖的 provider CLI(kimi-code / claude / codex / gemini / opencode)都是会自动升级的外部依赖 ,但我们对它们「接受什么参数、参数之间什么关系」的假设,目前只对着 mock 断言 。
结果就是:单元测试全绿,和线上 100% 失效,可以同时成立 —— 因为两者根本没在验证同一件事。
刚发生的实例(#1324 / 修复 PR #1323 ):
kimi-code CLI 从 0.29.1 升到 0.34.0,把 --agent-file 与 --session 从「静默忽略」改成「硬报错」
kimi 猫在任何 thread 里第一轮之后完全不可用
9 天运行时日志普查显示 76 次失败 ,分两种面孔:
48x unknown option '--agent-file'(更早的版本根本没这个 flag —— 这段时间 L0 身份注入是静默失效 的)
28x Cannot combine --agent/--agent-file with --session/--continue
整个期间 kimi-agent-service.test.js 39/39 全绿
最终是**人类 operator 察觉「kimi 最近老报错」**才被发现的,不是系统报出来的
mock 测试在结构上看不见这条裂缝。修完之后我做了真机验证,但那是手动一次性操作 ,不在任何自动流程里 —— 下一次同样保护不了我们。
Proposed solution
这个能力我们已经有了,只是只装在一个 provider 上。
packages/api/scripts/check-codex-app-server-protocol-census.mjs:
挂在 packages/api 的 build script 里,每次 build(含 CI)都跑
真实执行装着的 codex 二进制,导出它的实际协议 schema
与钉住的 fixture(test/fixtures/codex-app-server-thread-item-types.json)比对,漂移即失败
降级设计成熟:
try { census = loadInstalledCensus ( ) } // 装了 → 查真实二进制
catch { if ( ENOENT ) census = loadPinnedCensus ( ) } // 没装 → 退回 pinned fixture
装了 CLI 的机器查真实契约,没装的 CI 用 pinned —— 所以真正的保护发生在开发/运行机上 ,也正是事故发生的地方。
现状对比:
Codex
kimi / claude / gemini / opencode
build 期契约守卫
✅
❌
pinned fixture
✅
❌
真机契约验证
✅ 自动
❌ 无(仅临时手动)
packages/api/test/fixtures/ 下目前只有 codex 一个 census fixture。
提议 :把这个模式扩展到其他 provider CLI,从 kimi 开始。
kimi 的契约守卫应当断言三件事(都能用真实 CLI 在临时会话里跑):
新建会话带 --agent-file → 成功,且 agent 绑定生效
续接会话不带 --agent-file → 成功,且绑定的 agent 仍然生效 (这是 fix(kimi): drop --agent-file on session resume (kimi-code >=0.30 rejects the combination) #1323 修复所依赖的核心假设)
续接会话带 --agent-file → 被拒绝(把「我们知道它互斥」这件事本身钉住)
第 3 条是关键:它把我们的假设本身 变成可执行断言。哪天 CLI 又改语义(比如反过来允许了、或换成别的约束),这一步会红 —— 在 build 时红,不是在用户面前红。
这三条任意一条变化,都能覆盖本次事故的两种 面孔。
家里已有「真机 fixture」这个品类可复用(f254-codex-native-freshness-live-fixture、f210-agy-profile-smoke、windows-smoke.yml 里装 pinned opencode-ai@1.18.9),所以这不是新范式,是把已验证的做法补齐。
Alternatives considered
补更多单元测试 —— 无效。mock spawn 在原理上 不可能发现「真实 CLI 拒绝这串 argv」,这次 39/39 全绿就是证明。
钉死 CLI 版本 —— 做不到。用户自己安装、CLI 自动更新,我们不控制运行机上的版本。而且这只会把问题从「悄悄坏掉」变成「永远用不上新版」。
解析 --help 文本 —— 比 mock 强,但依赖散文措辞,CLI 改一句话就失效。直接跑真实命令更稳,也正是 Codex census 的做法(它导出 schema 而不是读 help)。
维持现状,坏了再修 —— 就是这次的代价:两天、76 次失败、一段 L0 静默失效期,最后靠人类察觉。
Additional context
由 @co-creator 建议立项;事故调查与证据来自 #1324 。
[宪宪/opus5🐾]
What problem does this solve?
我们依赖的 provider CLI(kimi-code / claude / codex / gemini / opencode)都是会自动升级的外部依赖,但我们对它们「接受什么参数、参数之间什么关系」的假设,目前只对着 mock 断言。
结果就是:单元测试全绿,和线上 100% 失效,可以同时成立 —— 因为两者根本没在验证同一件事。
刚发生的实例(#1324 / 修复 PR #1323):
--agent-file与--session从「静默忽略」改成「硬报错」unknown option '--agent-file'(更早的版本根本没这个 flag —— 这段时间 L0 身份注入是静默失效的)Cannot combine --agent/--agent-file with --session/--continuekimi-agent-service.test.js39/39 全绿mock 测试在结构上看不见这条裂缝。修完之后我做了真机验证,但那是手动一次性操作,不在任何自动流程里 —— 下一次同样保护不了我们。
Proposed solution
这个能力我们已经有了,只是只装在一个 provider 上。
packages/api/scripts/check-codex-app-server-protocol-census.mjs:packages/api的buildscript 里,每次 build(含 CI)都跑codex二进制,导出它的实际协议 schematest/fixtures/codex-app-server-thread-item-types.json)比对,漂移即失败装了 CLI 的机器查真实契约,没装的 CI 用 pinned —— 所以真正的保护发生在开发/运行机上,也正是事故发生的地方。
现状对比:
packages/api/test/fixtures/下目前只有 codex 一个 census fixture。提议:把这个模式扩展到其他 provider CLI,从 kimi 开始。
kimi 的契约守卫应当断言三件事(都能用真实 CLI 在临时会话里跑):
--agent-file→ 成功,且 agent 绑定生效--agent-file→ 成功,且绑定的 agent 仍然生效(这是 fix(kimi): drop --agent-file on session resume (kimi-code >=0.30 rejects the combination) #1323 修复所依赖的核心假设)--agent-file→ 被拒绝(把「我们知道它互斥」这件事本身钉住)第 3 条是关键:它把我们的假设本身变成可执行断言。哪天 CLI 又改语义(比如反过来允许了、或换成别的约束),这一步会红 —— 在 build 时红,不是在用户面前红。
这三条任意一条变化,都能覆盖本次事故的两种面孔。
家里已有「真机 fixture」这个品类可复用(
f254-codex-native-freshness-live-fixture、f210-agy-profile-smoke、windows-smoke.yml里装 pinnedopencode-ai@1.18.9),所以这不是新范式,是把已验证的做法补齐。Alternatives considered
--help文本 —— 比 mock 强,但依赖散文措辞,CLI 改一句话就失效。直接跑真实命令更稳,也正是 Codex census 的做法(它导出 schema 而不是读 help)。Additional context
packages/api/scripts/check-codex-app-server-protocol-census.mjs+packages/api/test/fixtures/codex-app-server-thread-item-types.json由 @co-creator 建议立项;事故调查与证据来自 #1324。
[宪宪/opus5🐾]