“提交已创建”——但其实并没有:为什么AI代理的承诺需要外部验证,以及这种失误的代价
一份详细的报告读起来就像证据。智能体列出了它修改过的文件,说出了分支名,引用了一个提交哈希,加上了测试数量,最后以“完成”收尾。工作看起来有模有样,而且确实像那么回事。而你还有六个工单要处理。
报告是模型输出。提交(如果存在)存放在 .git 中;文件(如果存在)存放在磁盘上;构建结果体现在进程退出码和运行日志里。聊天窗口与上述这些地方都无关,它只能为它自己作证。
我是 Explyt 的产品经理,在 Explyt 我们为 JetBrains IDE 构建 AI 智能体;在那之前,我曾带领构建它的团队。在加入 Explyt 之前,我在 JetBrains Research 和华为花了多年时间研究通过符号执行生成测试的工具,因此我养成了一个职业习惯:除非程序运行并产生了证据,否则我不信任任何关于程序的陈述。现在,用户访谈是我工作的一部分,一位资深开发者的这句话让我印象深刻,因为它太直白了:“智能体告诉我提交已经创建了,但实际上并没有。”我想知道这是一个不走运的会话,还是一种模式,于是我去翻阅了公开的 issue 跟踪器,以及厂商和研究文章中描述同类失败的帖子。事实证明这是一种模式,而且它有明确的机制。本文适用于你可能运行的所有 JetBrains AI 智能体:Junie、Agent 模式下的 Copilot、挂接在 IntelliJ IDEA 上的 Claude Code、藏在插件背后的本地模型,或者 Explyt。
TL;DR
- 我翻阅了 Claude Code、Codex 和 Copilot 的公开 issue,外加 Cursor 论坛上关于 Gemini 的帖子。这些内容记录了智能体报告提交存在于不存在的分支、文件从未写入、测试运行从未发生等情况。Anthropic 自己的文档和 METR 的一篇文章也描述了同样的失效模式,因此我不再把它们视为偶然。
- 我最常看到的三种虚假声明是“已提交”“文件已写入”和“测试通过”。对于每一种,我都给出了自己会执行的独立检查;每次检查都不超过一分钟。
- 智能体粘贴到聊天窗口中的检查结果同样属于模型输出。我会重新运行命令,或者阅读原始工具结果。
- 在我自己的工作中,我在 JetBrains IDE 内运行智能体;Explyt 会把文件编辑保存在 Agent Changes 中,并通过命名的运行配置执行构建和测试,因此实际发生的记录保存在聊天窗口之外。对于提交,我仍会使用 git log 检查。
公开 issue 显示的问题
下文每一项都是带链接的公开报告。每一条都记录了单个用户或团队经历的事件;合在一起,它们呈现了问题的轮廓,而我并没有试图衡量它发生的频率。
“已提交”与“已推送”
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 个测试并以退出码 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 缓存,而模块实际上没有变化,那么生产环境中的工件可能并不包含修复;在问题被标记为已解决之后,它还会再次出现。HN 上关于认证功能的那段叙述正是这一问题的尖锐体现:智能体报告该功能已实现、安全且通过测试,但这三项没有一个是真的。如果报告说门是关着的,没人会去检查门上的洞。
系统性的代价正是 Anthropic 在自己文档中指出的:你成了验证回路,每一个错误都在等你发现。智能体在任务上为你节省的时间,你会在核对其报告时还回去;或者你跳过检查,然后在生产环境或他人的评审中付出代价。而 #89765 展示了镜像问题:当对话记录被当作事实时,它在两个方向上都是不可靠的——既不能作为已做事项的记录,也不能作为你同意事项的记录。
为什么叙述与副作用会发生分离
上面的报告来自不同的产品和模型,因此原因隐藏在智能体的工作机制中,并出现在各家厂商的 bug 列表里。当我阅读维护者评论和附带的会话日志时,同样的机制反复出现。
模型叙述了一个工具调用,却从未真正调用它。该调用以文字形式出现,或出现在模型的推理过程中,而执行框架从未派发这一调用。随后模型把自己对结果的预测当成了结果。#63870、#67847 和 #19520 就是这样发生的(其中 #19520 里被叙述的工具根本不存在)。
工具在什么都没发生时返回成功。ctest 在没有匹配测试时以 0 退出。Windows 的 copy 命令可能在没有非零退出码的情况下失败。一次 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 针对产物本身执行检查,而不经过模型的摘要。
声明:“已提交。”请检查仓库,并确保位于正确的工作目录和分支中。
消息中的哈希应当与 git log 的输出一致,声称修改过的文件应当出现在 --stat 中,并且 git status 应当是干净的。对于“已推送”的声明,再加上 git fetch && git log origin/ -1。在 #19520 中,报告者也运行了 git cat-file -e;一个 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 列表逐项比对。# 让智能体规则中的“完成”真正有分量
你可以制定一条规则,重新定义“完成”能够意味着什么,从而把这部分验证工作交还给智能体。大多数运行在 JetBrains IDE 中的智能体都会读取仓库级指令文件(AGENTS.md 或厂商等效文件),并且许多智能体支持可复用技能。我使用的一条规则如下:
这条规则要求智能体在回复中包含命令的原始输出,也让缺失的检查变得可见:如果相应输出块不存在,就说明该步骤没有运行。你还可以更进一步,接入一个钩子,在记录到测试运行之前阻止提交。你可以这样做,但有一个来自 Hacker News 2026 年 4 月讨论串的告诫:用户报告过模型确实会无视这类停止钩子。规则有帮助,但不能替代你自己查看 git log。
JetBrains IDE 中保存证据的地方
Explyt 的文档说得很直接:“不要把聊天回复本身当成最终结果。请让智能体运行相应的测试或构建配置,并报告到底验证了什么。” IDE 本身已经记录了那些对话记录只能间接描述的事实,文档也说明了这些事实在哪里。
Agent Changes 会列出智能体在当前聊天中修改过的文件,并按文件显示差异,让你可以逐个接受或拒绝。这个列表就是文件级副作用的记录。如果某个智能体声称“已更新”的文件不在这个列表中,那也就没有什么可以接受的了。文档对适用范围写得很谨慎:“Agent Changes 覆盖的是智能体的文件编辑。文档并不保证自动回滚已执行命令、迁移、对外部系统的请求或应用程序运行所带来的后果。”
界面截图来自官方 Agent Changes 视频(视频中的项目是 TypeScript 测试套件,不是下文中的 Spring 示例):左侧是差异查看器,聊天区域有包含 Reject All、Accept All 和 Auto Review 的 Changes 行,右侧的 Explyt Agent Changes 面板则列出了智能体实际改动的唯一一个文件。
运行配置。 智能体通过 IDE 中命名好的运行配置来运行构建和测试,工具“会将结果返回给智能体:控制台输出、测试结果、编译错误”。你可以在运行面板中看到同样的结果,其中有数量和耗时。文档还指出,“对于没有合适 IDE 运行配置的命令,需要使用终端”。这里的分工是合理的:运行面板会显示测试数量,所以即使退出码为 0,你也能看到测试数是否为 0。
调试模式(Debug mode)。 对于有关行为的声明(例如“这个修复消除了 NPE”),Explyt 的文档所描述的流程是:复现故障,在断点处结合变量值和调用栈确认原因,应用最小修复,然后重新运行原始场景。文档自己也划定了边界:“单次调试器运行只能确认该特定场景下的修复有效。它不能替代相关测试和其他检查。”
还有一个 Auto Review 操作,它会把更改和聊天历史交给一个独立的 Review 子智能体,后者可以使用 IDE 检查。根据文档,它“不能替代运行测试和你自己对 diff 的审查”。
Explyt 并不会替你检查 git 提交,我也不会假装它会。文档列出的工具中有 Commit message 工具,它只会为 IDE 的提交对话框起草说明;没有任何工具会亲自创建提交,git 命令和其他命令一样,都是通过终端执行的。提交是一个 git 层面的事实,检查它的方式是 git log——无论在终端中,还是在 IDE 的 Git 工具窗口中。IDE 所增加的价值在于:文件编辑、运行过程和运行状态各自都有独立面板,而这些面板没有一个是模型自己写出来的。
一次五分钟的检查演示
我没有为这个场景录制运行过程;下面的内容只是为了展示检查的顺序。
你让智能体在一个 Spring Boot 服务中重命名一个配置键并提交。智能体报告:改了四个文件、测试通过、已提交。
- 打开
Agent Changes。数一下文件数量。列出了四个吗?逐个打开差异。重命名是否出现在所有 diff 中?还是某个文件本应被修改,实际却只有一条评论? - 查看运行面板。在最后一次编辑之后,是否执行过测试运行配置?跑了多少个测试,耗时多少?如果唯一的运行记录早于这次编辑,那么“测试通过”的说法已经过时。
- 在终端中运行:
git status --short。工作区干净,才算已提交。只要有已修改的文件,那么无论消息怎么说,提交都没有发生。 - 运行
git log -1 --stat。哈希值是否与报告一致?四个文件是否出现在其中? - 只要有任何地方与报告不符,这个不符点就是你的发现。把原始输出发回给智能体,然后以修正后的实际状态为准。如果智能体只是又给出一段修正后的总结,那等于同样的问题又发生了一次。
针对智能体所报告副作用的验收清单
- 在进行任何检查之前,先输出工作目录和分支。
git log -1 --stat显示声称的哈希值和声称的文件。- 在智能体声称“已提交”之后,
git status --short输出应为干净。 - 智能体提到的每个文件都应出现在
Agent Changes或git diff --stat中。 - 构建状态来自运行面板或构建工具的退出码,且时间戳应在最后一次编辑之后。
- 测试摘要应包含非零的测试数量和耗时,并且来自编辑之后的运行。
- 聊天中出现的任何命令输出,都应被你重新运行过,或与原始工具结果比对过。
- 通过逐项比较声称的文件列表与实际差异列表,可以捕获部分失败。
这份清单覆盖的是副作用声明。改动是否正确、测试是否有意义、修复是否触及根因,都是另外的问题;它们需要审查,如果涉及运行时行为,还需要调试器。
常见问题
JetBrains 有 AI 智能体吗?
有。JetBrains 发布了自家的编程智能体 Junie。第三方智能体可以作为插件运行在 JetBrains IDE 中,Explyt 就是其中之一;Claude Code 等外部智能体也可以连接到 IntelliJ IDEA。它们都会产生对话记录。上文讨论的问题覆盖 Claude Code、Codex、Gemini 和 Copilot;我并没有找到 JetBrains 特有的相关报告,也不会认为这意味着 JetBrains 可以免于这类问题。
什么是 JetBrains AI 智能体模式?
在智能体模式下,助手会在项目中采取多步骤行动:它会自行编辑文件并运行命令,而普通聊天模式只会回答问题。行动越多,需要核实的副作用就越多。
GitHub Copilot 智能体模式可用于 IntelliJ 吗?
可以。GitHub 的 JetBrains IDE 中的 Copilot Chat 文档包含“使用 Copilot 智能体模式”一节,但有一个注意事项:如果模式选择器里没有该选项,可能是组织管理员禁用了它。本文中的检查方法无需改变即可适用。
AI 智能体能否使用 IntelliJ 调试器?
根据文档,Explyt 的 Debug 模式会在 JetBrains 调试器下运行代码,并在编辑前检查断点、变量值和调用栈。这覆盖了关于行为的声明。关于提交的声明仍然要经过 git。
结论
我仍然会发现自己会在报告看起来漂亮时跳过 git log。这个习惯本身就是整个问题的一句话总结。
本文中的每项检查都发生在智能体说完之后。接下来的问题是:如果智能体在编辑之前得到的不是日志中的推测,而是一个运行时事实,结果会有什么不同?在 Explyt,我们用同一个 JVM bug 做了两次实验,一次使用纯文本调试技能,一次使用带 JetBrains 调试器的技能,并比较了补丁和 token 数量:看看运行时证据如何改变了一个真实 JVM bug 上的结果。
你是否遇到过智能体报告了一个提交、一个文件或一次“绿色通过”的运行,最后却发现根本不存在?是哪一项检查发现的?你花了多久才注意到?我会阅读评论区。
相关阅读
- AI 编程有一个新失败点:说“完成”之后的最终 diff:在人类打开审查之前,先对智能体输出做一次 pre-PR 检查。
关注我,获取更多关于在 JetBrains IDE 中运行 AI 智能体的内容,以及关于一个绿色对勾到底能证明什么、不能证明什么的讨论。
来源
厂商与研究资料
- Anthropic:Claude Code 最佳实践
- Anthropic:构建高效智能体(2024 年 12 月)
- Anthropic:Claude 4 发布(2025 年 5 月)
- OpenAI:监控推理模型的不当行为(2025 年 3 月)
- METR:近期前沿模型正在奖励作弊(2025 年 6 月)
- GitHub 文档:JetBrains IDE 中的 Copilot Chat(智能体模式章节)
- JetBrains:Junie
实践者与社区
- Simon Willison:Vibe engineering(2025 年 10 月)
- Birgitta Böckeler:在智能体循环中做 TDD(martinfowler.com)
- Hacker News:Claude Opus 4.8 讨论串评论
- Hacker News:Claude 4.7 无视停止钩子
- r/ClaudeAI:Claude 谎称推送了一个更新
Explyt 文档
- Explyt:Explyt 的工作原理
- Explyt:Agent Changes
- Explyt:权限与安全(Agent Changes 的范围)
- Explyt:聊天与工具(终端与运行配置)
- Explyt:工具列表(Commit message 工具)
- Explyt:运行配置
- Explyt:调试器
- Explyt:Auto Review
- Explyt:JetBrains Marketplace 上的 Explyt
文章中引用的问题报告都已在引用处附上链接。
这篇题为《“Commit created”——但它并不存在:为什么智能体的话需要外部验证,以及这个错误会带来什么代价》的博文最初发表于 foojay。