ByteNoteByteNote
写错位置的 !!js:文件系统工具静默失效复盘
字

字节笔记本

2026年10月6日 · 约 5 分钟读完

写错位置的 !!js:文件系统工具静默失效复盘

API中转
¥120

在 YAML 配置里给插件加条件开关,是很多框架用户的自然选择。DeepSeek Harness(一个基于 Cordis 的 Agent 运行时项目)的事故复盘 0002 就记录了这样一次翻车:开发者把文件系统插件的启用条件写成 disabled: !!js 表达式,结果表达式从未被求值,文件系统工具在所有模式下都处于禁用状态,快照测试却依然全部通过。问题最终靠人工评审发现,项目随后补上了配置层与测试层的双重护栏。这篇复盘对任何使用配置驱动框架的人都值得细读。

背景:bash-only 默认与快照场景的矛盾

默认的 ACP(Agent Client Protocol)示例组合有意只启用 bash 工具,因为它的沙箱只能约束 shell 进程,无法约束进程内的文件系统提供方。但文件系统快照场景又必须用到 read、write、edit 这三个工具。为了同时满足两边,这三个文件系统插件被放进默认的 cordis.yml,并附带一个 disabled 表达式,本意是只在全权限启动和快照模式下启用它们,其余时刻保持关闭。

事故机制:!!js 表达式从未被求值

机制:表达式对象从未被求值

问题出在对 !!js 作用范围的误解。Cordis 的 Include 会把每个 !!js 标量解析成一个表达式对象,但 Loader 只对插件的 config 字段做递归插值;像 disabled 这样的条目元数据被直接读取,不经过任何插值。于是每个文件系统条目看到的 disabled 值都是一个恒真的表达式对象,插件在所有模式下都保持禁用。更麻烦的是,YAML 标签在语法上完全合法,加载过程不产生任何诊断信息,静默失效就此发生。

影响:八个场景调用不存在的工具,快照却全绿

七个文件系统场景和一个混合工作区编辑场景,调用了注册表中根本不存在的工具。结构化会话日志里记录的是 ToolNotFoundError,错误码为 UNKNOWN_TOOL,而 stdout 只渲染出通用的失败工具卡片。快照套件之所以通过,是因为刷新后的预期输出恰好与这些失败输出一致:它证明的是回归的确定性回放,而不是文件系统行为的正确性。

值得注意的是,实际运行的受限默认模式并没有因此获得意外的文件系统访问权限。但一次天真的"修复插值"反而会带来这种风险:权限预设在运行时只能更新 bash 沙箱与审批状态,无法挂载、卸载或约束文件系统栈。换句话说,运行期的权限控制根本管不到组合期的文件系统挂载。

时间线:全绿检查与人工评审的对决

引入问题的 PR #261 整合了 ACP 组合并刷新了文件系统快照,同时加入条件式文件系统条目。所有单元测试、覆盖率、快照、文档、构建与卫生检查全部通过。转折发生在评审环节:有人注意到刷新后的预期输出里出现了通用失败卡片和结构化 UNKNOWN_TOOL 结果。随后一次真实的 Loader 启动确认,每个 disabled 值仍是表达式对象,每个文件系统 fiber 都没有被创建。

根因:两层失守

第一层失守在配置加载。实现时假设 !!js 作用于整个 Loader 条目,实际上只有 entry.options.config 会被插值,而 Entry.disabled 直接测试 entry.options.disabled,两者走的是完全不同的代码路径。第二层失守在快照框架。它把任何确定性的 transcript 都视为有效行为;header pin 虽然验证了组合后的工具 schema,但文件系统场景共享来自默认组合的 pin,没有独立证明自己所需的工具已注册。刷新在任何语义断言拒绝缺失工具之前,就已经重写了预期输出。

四道护栏

修复没有停留在改配置上,项目一共补了四道护栏。第一,文件系统场景改为启动 fs.cordis.yml,一个显式固定的全权限 overlay,配有成对的回放配置和独立的请求头类别。第二,AGENTS.md 与 Cordis 入门文档明确写出 !!js 仅在插件 config 内有效,条件组合应使用 overlay。第三,verify-cordis-config 会静态解析仓库中的 Cordis YAML,拒绝 Loader 条目元数据中出现表达式节点,包括 include patch 与插入条目。第四,dsh-acp-snapshot 拒绝结构化 UNKNOWN_TOOL 结果出现在全新运行和已提交的会话 fixture 中。

修复方案与四道护栏

三条教训

这场事故留下的教训可以推广到任何配置驱动框架。第一,语法上被接受的配置值,不一定会在那个位置被求值,要查阅并验证框架到底对哪些字段做插值。第二,快照刷新是 fixture 的生产过程,不是正确性审查,像注册工具缺失这种语义上不可能的结果,需要独立于预期输出的断言来兜底。第三,权限控制只应描述它实际管辖的能力,组合期的文件系统访问不能安全地跟随运行期的 bash-only 预设。

如果你也在用 YAML 给插件系统写条件开关,动手前值得确认三件事:表达式到底在哪个字段生效,加载期会不会静默吞掉语义错误,以及测试套件有没有独立于快照输出的断言。这三问的成本很低,却能避免一整类"测试全绿但功能缺失"的静默失效。

相关文章

分享: