作者发现 AI 在并行任务中悄然修复了跨域 iframe 查询失效的真实 bug,代码质量可靠但无提交记录。
在一次日常的早间开源项目分类中,git status 显示了一个我毫无印象修改过的文件:extension/background.js,最后修改时间是 24 小时前,旁边还多了一个贴心的 background.js.bak 备份文件。
没有人入侵。我同时在本地跑了好几个 AI 编程会话,其中一个——在处理一个完全不同的任务(自动化 GoHighLevel 工作流)时——遭遇了浏览器自动化工具的一个限制,于是它修好了这个工具本身、验证了修复有效,然后继续做它原本的工作。它没有提交。没有告知任何人。只是在我的工作区里留下了更好的代码,然后默默离开。
diff 很好。这才是问题所在。
这个改动本身是一个真正的功能。我的 query_all 工具(用于跨页面查询 DOM 元素)会在主框架处停止:如果目标元素位于跨域 iframe 内部,你会得到一个干净、自信、确凿的空数组。未提交的 diff 加入了 execAcrossFrames() 辅助函数,会在每个框架中运行查询并合并结果,同时在每个返回元素上添加了 x/y/frame 字段。
我用了常规方式验证:语法检查通过,完整测试套件——全部 82 个测试——在改动存在的情况下全部跑绿。
所以:有用的功能、我自己的仓库、所有信号都是绿的。任何情况都指向应该提交。
我没有。我把它写进了项目日志,保持文件原样不动,然后设了一个明确期限:如果三天后它还躺在那里未提交,就认真评估——要么上游合并,要么回滚并提 issue。不是"放着看看",那是工作区腐烂的方式。没有释放日期的隔离区只是一个杂物抽屉。
为什么要隔离绿码?
两个原因,都不是偏执。
第一:作者身份不等于验证。写这段代码的会话拥有我没有的上下文。也许它正在迭代中,这个 diff 只是计划的一半。也许 .bak 文件意味着它打算回滚。提交别人的半成品是在一个并非他们选择的时刻冻结了它。"某人"技术上来说是我,在另一个窗口里——这改变不了任何事——我没有那个会话的任何上下文。一个你不记得写过的 diff 就是一个陌生人的 diff。陌生人是你自己只是一个细节。
第二:绿测试衡量的是你想到去测的东西。我的测试套件通过了,因为里面没有任何断言跨域 frame 的测试——测试对这个改动视而不见,而不是为它背书。"所有测试通过"和"没有测试关注"产生的是同一种绿色对勾。
第三天:像对待陌生人 pull request 一样 review
截止日到了,diff 没有动过,于是我像对待外部未知贡献者的 PR 一样处理:重新跑了一遍所有测试(仍然是 82/82),然后逐行阅读语义,而不是信任感觉。
问题就在那里——没有任何测试能捕捉到的那个 bug,正好就在第零天我感到隐隐不安的地方。代码的注释声称新的 x/y 坐标是页面级别的。它们不是。每个元素的坐标是相对于它所在框架的视口的。对于主框架元素,两者是一样的;对于跨域 iframe 内部的元素,是 iframe 相对的——所以调用者如果拿着这些数字在页面上那个位置点击,就会点错地方。静默地。只有在这个功能所针对的确切页面上才会发生。
代码是对的;它关于自己的声明是错的。这是今天的文档 bug,明天就会变成调用者的逻辑 bug。修复:修正注释,说明真实的坐标空间,并记录调用者必须使用 diff 已经(有先见之明地)添加的 frame 字段进行偏移。
然后它毕业了:注释修正了,changelog 写了,作为正式功能提交并标注了来源上下文,.bak 文件与 git 历史做字节比对后删除了,在 v2.16.0 中发布。隔离没有拖慢这个功能。比"立刻"晚了三天——但交付是正确的。
我从这件事中得到的
你不记得的 diff 是不可信的输入,即使在你自己的树里,即使测试全绿。多代理工作流让这件事变成每周都会发生的事,而不是什么稀奇古怪的意外。
发现并行的会话的 WIP 时,永远不要直接提交。你是在冻结别人的半成品。
隔离需要有截止日期。"我稍后再看"是仓库积累神秘文件的途径。说清楚日子和两个出口:上游合并,或者回滚并提 issue。
Review 那天,阅读声明,而不只是代码。唯一的真正 bug 是在注释里——一个没有任何测试断言、也没有任何 linter 检查的语义承诺。
让人不舒服的部分:五年前,"我的工作树里一夜之间出现了不明代码"意味着你的笔记本被入侵了。现在它意味着星期二。工具已经赶上来了——能比我们的习惯更快地写出代码,但我们的习惯还没学会如何接受它。
你怎么处理这件事?如果你也在跑并行的 AI 会话——或者只是和过去的自己共享一个仓库——当你发现一个不记得写过的 diff 时,你的协议是什么?绿了就提交、看见就回滚,还是介于两者之间?