Ohhnews

分类导航

$ cd ..
foojay原文

“提交已创建”并非事实:AI 智能体的表述为何需要外部验证,错误代价有多大

#ai 编程助手#代码提交#测试验证#版本控制#开发者工具

[LOADING...]

一份详细的报告读起来就像证据。智能体列出了它动过的文件,给出了分支名,引用了一个提交哈希,添加了测试数量,最后以“完成”收尾。这项工作有了一个形态,而且形态看起来是对的。你还有六个工单要处理。

但报告只是模型输出。提交(如果存在)位于 .git 中。文件(如果存在)位于磁盘上。构建结果存在于进程退出码和运行日志里。聊天窗口不是上述任何一个地方,它只能为自己的话作证。

我是 Explyt 的产品经理,Explyt 正在为 JetBrains IDE 构建 AI 智能体;在那之前,我领导了构建它的团队。在加入 Explyt 之前,我在 JetBrains Research 和华为花了多年时间开发通过符号执行生成测试的工具,所以我养成了职业习惯:对任何关于程序的说法,都要等到有东西真正运行并产生证据之后才相信。用户访谈现在是我工作的一部分,一位资深开发者的一句话让我印象深刻,因为实在太直白了:“智能体告诉我提交已经创建。但它没有。”我想知道这只是一次不走运的会话,还是一种普遍模式,于是我去翻阅了公共问题跟踪器,以及所有描述过同样失败的厂商和研究文章。这是一种模式,而且它有明确的机制。本文适用于你可能运行的任何 JetBrains AI 智能体:Junie、智能体模式下的 Copilot、附加到 IntelliJ IDEA 的 Claude Code、插件背后的本地模型,或者 Explyt。

太长不看

  • 我翻阅了 Claude Code、Codex 和 Copilot 的公共 issue,外加 Cursor 论坛上一条关于 Gemini 的帖子。它们记录了智能体报告过不存在的分支上的提交、从未写出的文件,以及从未发生的测试运行。Anthropic 自己的文档和 METR 的一篇文章描述了同样的故障模式,所以我不再把它当成运气不好。
  • 我见到最多的三种虚假声明是“已提交”“文件已写入”“测试通过”。针对每一种,我都给出了我自己会做的独立核验;每一种花费都不超过一分钟。
  • 智能体粘贴到聊天窗口里的核验结果同样属于模型输出。我会重新运行命令,或者去读原始工具结果。
  • 在我自己的工作中,我会在 JetBrains IDE 里运行智能体;Explyt 将文件编辑保留在 Agent Changes 中,并通过具名运行配置执行构建和测试,因此实际发生过的记录存在于聊天之外。提交我仍然会用 git log 检查。

问题跟踪器显示了什么

下面每一项都是带链接的公开报告。每一条都是来自某个用户或团队的记录在案的事件;它们合在一起呈现了问题的形态,而我并没有试图统计它发生的频率。

“已提交”与“已推送”

anthropics/claude-code #63870(2026 年 5 月,本列表中获赞最多的一条)中,智能体把它的 Bash 调用以纯文本打印出来,却从未真正派发执行。会话日志中没有这些调用的工具结果。报告称:“提交、验证命令、推送、PR 创建、PR 合并和清理命令都被打印了出来,但并没有真正执行。”

anthropics/claude-code #67847(2026 年 6 月)记录了模型声称自己在某文件中追加了一段说明,并引用了追加后的文本,但 API 响应中不包含任何工具使用块。“随后真正执行 git 提交时返回了 ‘nothing to commit’,这才暴露了问题。”

反过来也一样。在 openai/codex #22219 中,智能体正确地执行了提交,却在最终消息中“把这些文件列为‘当前未提交’,尽管这些文件正是它刚刚提交的,而且 git status --short 显示工作区是干净的”。一份无论朝哪个方向与仓库实际状态不一致的报告,都是你无法采用的报告。

“文件已写入”

anthropics/claude-code #23801(2026 年 2 月,Windows):一次复制命令静默失败,但模型说文件已保存。“Claude Code 报告‘完成,文件已保存’,却未检查文件是否真的存在。”直到用户追问了两次、模型最终运行了一次目录列表之后,文件缺失的问题才暴露出来。

“测试通过”

microsoft/vscode-cmake-tools #4915(2026 年 4 月)是我找到的机制最清晰的案例,因为维护者诊断并修复了它。Copilot 智能体用了一个匹配不到任何测试的测试名调用 ctest 工具。ctest 运行了零个测试并退出 0。智能体“随后告诉我所有测试都通过了。但它实际上没有运行任何测试。”维护者评论说:“由于 ctest 在测试通过和没有测试匹配时都返回退出码 0,工具会报告成功,而智能体无法区分这两种情况。”修复后来被加入到了工具中。

openai/codex #41626(2026 年 8 月):智能体请求运行硬件挂起/恢复测试,用户从未授予权限,但自动生成的会话摘要后来“声称 HDMI 挂起/恢复测试已通过,任务已完成”。

两则个人经历让这幅图景更完整。在 Hacker News 的 Claude Opus 4.8 发布帖中(评论于 2026 年 5 月),一位用户写道,之前的模型“报告称它已经创建了认证功能、一切都很安全、测试也都通过了。问题在于它实际上并没有实现认证功能”。在 r/ClaudeAI(2025 年 7 月)上,有人描述了一次智能体声称已经推送的 Supabase 迁移:“这一次,它说它做了……但它没有做。”

这个错误要付出什么代价

最轻的后果是丢失工作。改动未暂存地留在工作副本中;下一次切换分支、执行 stash 或运行智能体时,它们就会被带走,开发者只能等到“已完成”的改动从分支上消失时才发现。在 #63870 中,缺失的是提交、推送、PR 和清理工作,而那位开发者早已继续往前走了。

再往上一个层级,是浪费别人的时间。评审者打开智能体所描述的 PR,搜索那个哈希,却发现它既不在任何分支上,也不在任何对象库中(#19520)。任务板上写着完成,站会上也说完成,但仓库与这两者都意见不一致。现在,必须有人重新梳理实际到底合入了什么。

然后是那个从未发布出去的热修复。如果“构建通过”来自 Gradle 或 Maven 对未变更模块的缓存,那么生产环境中的制品可能并不包含这个修复;于是,事故会在被标记为“已解决”之后再次回来。Hacker News 上关于认证功能的那段叙述正是这件事最尖锐的一端:智能体报告功能已实现、已安全、已测试,而三项没有一项是真的。如果报告说门是关着的,就不会有人去找门上的洞。

系统性代价就是 Anthropic 在自己的文档中点名的那一点:你成了验证循环,每一个错误都在等着你发现。智能体在任务上帮你省下的任何时间,你都要在核验它的报告时还回去;或者你跳过核验,以后在生产环境或别人的评审中再偿还。而 #89765 则给出了镜像版本:当聊天记录被当作事实时,它在两个方向上都是不可靠的——既不能作为“做了什么”的记录,也不能作为“你同意了什么”的记录。

为什么叙述和实际副作用会脱节

上述报告来自不同产品和模型,所以根源在于智能体的工作方式本身,并且出现在每个厂商的 bug 列表中。阅读维护者评论和附带的会话日志时,同样的机制一再出现。

模型叙述了一次工具调用,却从未真正执行。 该调用只作为文本出现,或出现在模型推理中,执行框架从未真正派发它。模型随后把自己对结果的预测当成了真实结果。这正是 #63870#67847#19520 中发生的事情(其中被叙述的工具根本不存在)。

工具在什么都没发生时返回成功。 ctest 匹配不到测试时退出 0;Windows 的复制命令可能在失败时不产生非零退出码;一次 py_compile 通过会被报告为“已验证”。智能体只看到一个干净的退出码,却无从知道这次检查是空的。#4915#23801 就是这种情况。

摘要层覆盖了真实的最后一步。 会话回顾、任务状态跟踪器和“对话摘要”是根据模型对工作的理解生成的,而这种理解可能落后于实际最近一次工具结果。#22219#41626 展示了摘要宣布工作已完成,而会话记录本身显示它仍处于待处理状态。

测试范围比声称的更窄。 单元测试一路变绿,但 bug 位于它们绕过的那一层;于是“测试通过”被升级为“已修复”。anthropics/claude-code #37818 中那个交易系统报告持续了数周,正是这种失败一次又一次地重演。

模型伪造授权。 在 anthropics/claude-code #89765(2026 年 8 月)中,模型在自己的消息里写入了一段用户同意推送的话,然后“基于伪造的批准进行了提交和推送”。权限提示最终阻止了它。这是最让我念念不忘的一个案例。一个能给自己写许可条的工具,和一个误读退出码的工具,属于完全不同的问题;除了让权限提示保持开启之外,我并没有一个干净利落的答案。

Anthropic 在自己的文档中也说了同样的话。Claude Code 最佳实践指南写道:“当工作看起来完成时,Claude 就会停止。如果它没有可运行的检查,“看起来完成”就是唯一可用的信号,于是你成了验证循环:每个错误都在等着你发现。”它早期关于智能体的工程文章则从正面表达了这一点:“智能体必须在每一步都从环境中获取“真值”(例如工具调用结果或代码执行),以评估其进展,这一点至关重要。”

研究领域走得更远。OpenAI 2025 年 3 月关于推理模型监控的论文发现,在训练中,编码智能体“很快就学会了修改测试框架,让测试轻易通过,而不是实现真正的解决方案”。METR 在 2025 年 6 月的文章中报告:“最新的前沿模型开始进行日益复杂的奖励黑客行为,试图(而且常常成功)通过修改测试或评分代码来获得更高分数。”Anthropic 的 Claude 4 发布公告声称,与 Sonnet 3.7 相比,走捷径和钻漏洞的行为减少了 65%。这是好消息,同时也印证了原本有多少这类行为需要减少。

Simon Willison 在他的《Vibe engineering》一文中用一句话概括了实际后果:“没有测试?你的智能体可能会声称某个东西能工作,而实际上它根本没有测试过。”Birgitta Böckeler 在 martinfowler.com 上研究智能体循环中的 TDD 时,从另一面看到了同样的事情:“智能体有时仍会跳过或伪造红灯步骤,或者先于测试实现功能,让测试立即通过。”

三种声明,三种独立核验

独立核验意味着:由你或 IDE 执行,直接针对制品本身,而不经过模型的摘要。

声明:“已提交。”在正确的工作目录和分支下检查仓库:

$ bash
pwd
git branch --show-current
git log -1 --stat
git status --short

消息中的哈希应与 git log 一致,声称的文件应出现在 --stat 中,且 git status 应为干净状态。若要验证“已推送”,再加上 git fetch && git log origin/<分支> -1。在 #19520 中,报告者还运行了 git cat-file -e <hash>;一个 Git 从未见过的哈希是结束对话最快的方式。

声明:“已创建或更新文件。”检查文件系统和 VCS 差异。ls -la path/to/File.kt 查看存在性和时间戳,git diff --stat -- path/to/File.kt 查看内容。在 IDE 中,Git 工具窗口或智能体自己的更改列表会显示同样的信息,而不必离开编辑器。一个声称“已写入”却没有出现在任何 diff 中的文件,实际上并没有被写入。

声明:“构建通过”或“测试通过。”检查产生该结果的进程。构建声明需要退出码,以及不早于最近一次编辑的时间戳。测试声明需要测试数量、测试类,并确认运行发生在改动之后。在 JetBrains IDE 中,运行面板会给出这一切,包括失败的断言(如果有的话)。像“全部 38 个通过”这样没有对应运行的数字只是装饰;正如 #4915 所示,“0 个测试,退出 0”同样只是一个数字。

每次核验耗时不到一分钟。

陷阱:看似核验实则伪造

一旦你开始核验,智能体(或者你自己的习惯)就会提供各种捷径。

第一个是智能体替你粘贴核验结果。“这里是 git log 以作确认:”后面跟一个代码块。那个代码块是模型输出。除非执行框架把原始工具结果与模型文本分开显示,否则请把粘贴的命令输出当成一条声明,并自己去运行命令。

第二个是在错误的目录里运行正确的命令。多模块仓库和 git worktree 很容易让你核验到另一个检出副本。检查前先打印工作目录。我自己就不止一次犯过这个错,所以上面第一行才是 pwd

第三个是缓存带来的绿灯。当输入没有变化时,Gradle 和 Maven 会从缓存报告成功。如果构建对你预期会变更的模块显示 “up to date”,那么变更可能根本不在那里。

第四个是空的测试选择,以及它的近亲:只有测试数量,却没有测试运行。一个匹配不到任何内容的过滤器、一个拼写错误的测试名、一个排除掉目标类的 profile:运行器以 0 退出,智能体报告绿灯。数一数 @Test 注解也算不上运行。要看测试数量、时长和时间戳。

第五个是分支不匹配。提交确实存在,但它在某个没人要求的分支上。git log --all --oneline -5 可以捕获这个问题。

最后一个是把部分成功报告为完全成功。五个文件中写入了三个,另外两个被权限规则拒绝,消息却说“已更新这些文件”。把声称的清单和 diff 清单逐项对比。# 让“完成”在智能体的规则中真正算数

你可以通过一条规则,把一部分这类验证工作交还给智能体;这条规则用来重新定义“done(完成)”允许意味着什么。运行在 JetBrains IDE 中的大多数智能体都会读取仓库级指令文件(AGENTS.md 或厂商对应的等效文件),许多还支持可复用技能。我使用的一条规则是:

$ java
## Reporting side effects

After any action that changes state outside the chat (git commit, git push,
file create/delete, build, test run), do not describe the result in prose.
Run the corresponding check and include its raw output:

- commit  -> `git log -1 --stat` and `git status --short`
- file    -> `git diff --stat -- <path>` (or `ls -la <path>` for new files)
- build   -> the build tool's final status line and exit code
- tests   -> the test runner summary with counts and duration

If the check fails, returns an error, or runs zero tests, say so first,
before any summary. Never report a step as completed if its tool call
did not return success.

这条规则要求智能体在其回合中附上原始命令输出,也让缺失的检查变得可见:如果输出块不存在,就说明这一步没有运行。你还可以更进一步,接入一个钩子(hook),在没有记录到测试运行之前阻止提交。这样做是可以的,但要注意 2026 年 4 月 Hacker News 上一个帖子提到的警示:有用户报告某模型恰恰忽略了这类停止钩子。这条规则有帮助,但它不能替代你自己查看 git log

JetBrains IDE 在哪里保留证据

Explyt 的文档直截了当地写道:“不要只把聊天回复当作一个已完成的结果。要让智能体运行相应的测试或构建配置,并报告到底验证了什么。” IDE 本身已经记录了那些对话记录只能转述的事实,文档也说明了这些记录在哪里。

Agent Changes 会列出智能体在当前聊天中修改的文件,每个文件都带有 diff,并允许你逐一接受或拒绝。这个列表就是文件级副作用的记录。如果智能体声称“更新”过的文件不在列表中,那你就没有可接受的东西。文档在范围上很谨慎:“Agent Changes 涵盖的是智能体的文件编辑。文档并不保证能自动回滚已执行命令、迁移、对外部系统的请求或应用运行所带来的后果。”

Explyt 在 JetBrains IDE 中完成任务后的界面:左侧是 diff 查看器,聊天里有带有 Reject All、Accept All 和 Auto Review 的 Changes 行,右侧 Explyt Agent Changes 面板列出了智能体所改动的唯一文件。截图来自官方 Agent Changes 视频;该项目是 TypeScript 测试套件,不是下文中的 Spring 示例。

运行配置。 智能体通过 IDE 的命名运行配置来执行构建和测试,该工具“会把结果返回给智能体:控制台输出、测试结果、编译错误。” 运行面板也会展示同样的结果,包括数量和耗时。文档还指出“没有合适 IDE 运行配置的命令才需要使用终端”;这是合理的分工:运行面板会显示测试数量,所以即使退出码是 0,你也能看到“零测试”的情况。

Debug 模式。 对于行为层面的断言(例如“这个修复消除了 NPE”),Explyt 的文档说明它会复现故障,在断点处通过变量值和调用栈确认原因,应用最小修复,然后重新运行原始场景。文档自己也划定了边界:“一次调试器运行只能确认修复在该特定场景下有效。它不能替代相关测试和其他检查。”

还有一个 Auto Review 操作,它会把改动和聊天记录交给一个独立的 Review 子智能体,后者可以使用 IDE 的代码检查能力。按文档说法,它“不能替代你自己运行测试并检查 diff。”

Explyt 不会替你检查 git 提交,我也无意假装它会。文档中的工具列表里有一个 Commit message 工具,可以起草 IDE 提交对话框中的描述;没有任何工具会真正创建提交,git 命令也与其他命令一样通过终端执行。提交是一个 git 事实,检查它要运行 git log——无论是在终端里还是在 IDE 的 Git 工具窗口中。IDE 真正增加的,是让文件编辑、运行结果和运行时状态各自拥有独立面板,而这些面板中的内容没有一个是模型写出来的。

五分钟走查

我没有为这个场景录制实际运行过程;这里只演示检查顺序。

你让智能体在一个 Spring Boot 服务中重命名一个配置键并提交。智能体报告:改了 4 个文件、测试通过、已提交。

  1. 打开 Agent Changes。数一下文件。列出了 4 个?逐个打开 diff。重命名是否都在其中,还是某个文件本应改动,实际却只得到了一条注释?
  2. 查看运行面板。在最后一次编辑之后,是否执行过测试运行配置?跑了多少测试、用了多长时间?如果唯一一次运行早于编辑,那么“测试通过”的说法已经过时。
  3. 在终端中运行:git status --short。干净表示已提交;仍有已修改文件则说明提交并没有发生,无论消息怎么说。
  4. 运行 git log -1 --stat。哈希对得上吗?那 4 个文件都出现了吗?
  5. 如果任何一项与报告不一致,不一致本身就是问题。把原始输出发回给智能体,然后采用被纠正后的实际状态。只接受一个纠正后的摘要,等于又回到了原来的问题。

智能体报告副作用时的验收清单

  1. 在任何检查前先输出当前工作目录和分支。
  2. git log -1 --stat 显示所声称的哈希和所声称的文件。
  3. 在报告“已提交”之后,git status --short 是干净的。
  4. 智能体提到的每个文件都出现在 Agent Changesgit diff --stat 中。
  5. 构建状态来自运行面板或构建工具的退出码,且时间戳在最后一次编辑之后。
  6. 测试摘要包含来自编辑后某次运行的非零测试数量和耗时。
  7. 聊天中粘贴的任何命令输出,都必须由你重新运行过,或与原始工具结果核对过。
  8. 把声称的文件列表与实际 diff 列表逐项比对,从而发现部分失败。

这份清单针对的是副作用声明。改动本身是否正确、测试是否真有意义、修复是否命中根本原因,是另外的问题;它们需要代码审查,涉及运行时行为时还需要调试器。

常见问题

JetBrains 有 AI 智能体吗?

有。JetBrains 发布了自己的编程智能体 Junie。第三方智能体可以以插件形式运行在 JetBrains IDE 中,Explyt 就是其中之一;像 Claude Code 这样的外部智能体也可以接入 IntelliJ IDEA。它们都会产生对话记录。上面提到的问题覆盖 Claude Code、Codex、Gemini 和 Copilot;我没有找到专门针对 JetBrains 的报告,但不会因此把它解读为 JetBrains 就能幸免。

JetBrains AI agent 模式是什么?

在 agent 模式下,助手会在项目中自主采取多步操作:它自己编辑文件、运行命令;而普通聊天模式只会回答问题。操作越多,需要验证的副作用也就越多。

GitHub Copilot 的 agent 模式在 IntelliJ 中可用吗?

可用。GitHub 的 JetBrains IDE 中的 Copilot Chat 文档中包含 “Using Copilot agent mode” 一节。有一个注意事项:如果模式选择器中看不到该选项,可能是你的组织管理员禁用了它。本文中的检查方法不变。

AI 智能体可以使用 IntelliJ 调试器吗?

Explyt 的 Debug 模式按文档说明会让代码在 JetBrains 调试器下运行,并在编辑前检查断点、变量值和调用栈。这适用于对行为层面的断言;关于提交的断言仍要通过 git 来验证。

结论

报告看起来很整齐时,我仍然会发现自己跳过 git log。这个习惯用一句话概括就是整个问题所在。

本文中的每一个检查都发生在智能体说完话之后。下一个问题是:如果智能体在编辑之前就拿到一个运行时事实,而不是来自日志的推断,结果会发生什么变化?在 Explyt,我们把同一个 JVM bug 运行了两次:一次使用仅文本的调试技能,另一次使用带 JetBrains 调试器的技能,然后比较了补丁和 token 数量:看看运行时证据如何改变了一个真实 JVM bug 的结果

有没有智能体向你报告过一个提交、一个文件或一次绿色通过的测试,结果却根本不存在?是哪个检查抓住了它,你花了多久才发现?我会看评论区。

延伸阅读

想了解更多关于在 JetBrains IDE 中运行 AI 智能体的内容,以及绿色勾选到底能证明什么、不能证明什么,欢迎继续关注。

参考资料

供应商与研究机构

实践者与社区

Explyt 文档

文中引用的 issue 报告已在对应引用处给出链接。

本文(«Создано коммит»——但这并不是真的:为什么智能体的话需要外部验证,以及这个错误要付出多大代价)首发于 foojay