跳转到主要内容

你的编程代理基于落后 Main 分支三周的代码版本重建的代码库索引

阅读需 2 分钟Tian PanTian Pan

你团队中的一个 AI 编程 Agent 提交了一个 PR,在两个文件中调用了四次 parseUserToken()。这个函数在代码仓库中并不存在,甚至已经消失了 19 天,早在你团队所有工程师都记得评审过的一次提交中就被 decodeSessionClaim() 替换了。Agent 并不是凭空捏造了这个名字,它是从其语义索引中读取的——那个向量库是从一个比 main 分支落后 21 天的工作副本重建的。相比之下,Agent 的编辑步骤在会话开始时运行了 git pull,操作的是最新的代码。对同一个代码库的两个视角,相隔三周,而 Agent 却自信地用一段无法针对任何真实环境编译的代码桥接了它们。

这是一种不会自我宣告的失败模式。Agent 运行了。测试看起来通过了。PR 合并了。第一位评审者之所以注意到,仅仅是因为一个被删减的函数与一个无关的辅助函数重名,触发了 linter 报错。到那时,Agent 已经花了一个完整的冲刺(sprint)针对一个“幻影版本”的代码库进行编写,而团队中没有一个人——包括 Agent 自己——收到任何异常信号。

Agent 对代码库的“理解”与代码库的“实际状态”之间的缝隙,是一个没人会在架构图上画出来的“一致性边界”。索引由一个团队按一种节奏更新;工作树(working tree)由另一个团队按另一种节奏获取。Agent 的推理存在于第一个层面,而 Agent 的动作落在了第二个层面。当两者即便只差几次提交——更不用说三周——结果就是一个 Agent 会基于一个已不存在的代码库,提出极其自信的主张。

偏差是如何产生的

索引和工作树是平台回答“这个仓库里有什么代码”的两种方式。大多数 Agent 平台将它们存放在不同的地方,按不同的时间表刷新,并假设结果是一致的。事实往往并非如此。

最常见的偏差来源是镜像缓存(mirror cache)。为了应对 GitHub 的速率限制并加速冷克隆(cold clones),Agent 平台会为每个跟踪的仓库保留一个本地镜像,并定期(每隔几分钟、每小时甚至更久)从 origin 刷新。多年来,这种镜像一直是与 CDN 类似的基础设施;像 gitcachegit-cache-clone 这样的工具对此有明确说明。刷新间隔是可配置的,而问题正出在这里。如果刷新任务在平台迁移期间被暂停、限速、误配置或遗忘,镜像就会在无形中落后于 origin。索引从镜像中提取,而工作树直接从 origin 提取。两者因此产生分歧。

其他诱因则更为微妙。出于性能考虑,语义索引在不同会话之间是持久化的——重新计算嵌入(embeddings)成本高昂,大多数 Agent 平台都会积极复用它们。在大规模分支切换、重写了数千个文件的依赖项升级、生成的代码重新生成或 LFS 拉取之后,索引可能会携带指向已不存在的代码的孤立条目。Cursor 的文档承认了这一点:当自动补全开始引用你三个分支前删除的文件时,说明索引中存在陈旧条目。Cursor 用来增量重新索引的 Merkle 树变化检测器在查找“已修改”文件方面效率很高,但在结构性重写时,它的失效处理并不总是足够激进。

第三个诱因是索引失败但检索未失败。索引器在运行中途崩溃,留下了一个不完整的索引,而 Agent 平台继续根据最后一次索引的内容提供搜索结果。Agent 无法知道它的结果中现在选择性地遗漏了最后六次提交的更改。搜索结果依然自信地返回。这种“自信”正是问题所在。

Agent 在陈旧视图下会做什么

编程 Agent 使用索引做两件事:搜索(“帮我找到处理身份验证的函数”)和 Grounding(知识对齐,“这是我要调用的函数签名”)。两者都以不同的方式受到陈旧性的威胁。

搜索功能的退化是悄无声息的。陈旧的索引会返回“过去”实现某个功能的某个文件,Agent 会将其视为真理。如果该文件已被拆分、重命名或合并到另一个模块中,那么从第一次搜索开始,Agent 关于逻辑所在位置的心智模型就是错误的。后续的搜索无法修复这一点——它们通常会变本加厉,因为 Agent 的后续查询是基于第一次搜索返回的内容。

Grounding 的失败有时会表现得更明显。如果 Agent 调用了一个不再存在的函数,编译器通常会捕获它——故事本该到此结束。但现代编程 Agent 的循环是“迭代直到测试通过”,而该循环的许多实现都在构建失败时提供了一个后备方案:删减调用、模拟(mock)返回值、注释掉报错的代码块。Agent 的提示词告诉它要让测试通过,于是它通过移除报错的部分来让测试通过。这个临时方案就这样被发布了。评审者看到 CI 运行通过且 diff 干净,便予以批准。

最阴险的情况是“部分重叠”。Agent 调用的函数确实存在,但其签名与索引记忆的不同。参数顺序改变了,增加了一个必填参数,或者返回类型被缩小了。代码在某些情况下可以编译,但在运行时会崩溃。Agent 永远不会收到明确的信号表明其 Grounding 是错误的,因为这种错误是间歇性的。

会员专享

余下内容仅对会员开放。

会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。

  • 完整文章,包含未公开存档的部分
  • 可落地的工作框架,附带权衡与决策依据
  • 新文章抢先看,先于公开发布

随时取消 · 一次订阅,畅读全部

参考资料

保持联系,关注我获取更多内容

阅读需 11 分钟

你的编程智能体生成的那些人类已经不再阅读的 PR 描述

PR 描述模板在由人类编写且格式承载关键信号时非常有效。而智能体生成的描述消除了差异性,导致审查者产生习惯化心理,使得审查流程悄然绕过了它原本依赖的交付物。

insider
coding-agents
阅读需 10 分钟

你的编码 Agent 写不出的 PR 描述

Agent 提交了干净的 PR,描述却是空的;异步评审因此失灵 —— 推理过程藏在脚手架早已丢弃的提示词里。

insider
ai-engineering
阅读需 9 分钟

AI 代码审查倒置:当作者是机器时应关注什么

当 AI 智能体编写了你大部分的提交时,逐行代码的正确性审查会忽略那些关键的漏洞。这里有一套真正适用于机器创作代码的审查规范。

insider
ai-engineering
阅读需 10 分钟

你的编程 Agent 开启的那个导致真实 PR 被关闭的拉取请求

当一个编程 Agent 根据过时的评论写下 “Closes #1247” 时,GitHub 会将其视为一条关键指令。本文探讨了 Agent 编写的 PR 元数据如何破坏人工审查工作,以及防止这种情况发生的门控机制。

coding-agents
github
阅读需 10 分钟

那些在本地通过但在 CI 中失败的编程智能体

编程智能体会继承你笔记本电脑中已配置好的环境,并提交在本地通过但在 CI 中失败的补丁。解决方案是建立仓库级的环境契约、进行预检一致性检查,并根据本地与 CI 的差异进行评分。

insider
ai-engineering