ARTICLE / ai
挂了、丢了、谎报成功:AI 编码 CLI 的失败都在沉默里——五个真实案例复盘
给编码智能体派了个活,它跑了半天回来说"完成了"——你怎么知道它是真完成了,还是把一次中断包装成了成功?过去几个月里,Claude Code、GitHub Copilot CLI、Gemini CLI、OpenAI Codex 等主流编码 CLI 的仓库中,一批高热度缺陷指向同一个模式:失败发生了,但用户看不到。更糟的是有些失败会被伪装成成功,或者干脆把整个会话锁死。
这篇文章梳理五个有完整复现记录的真实案例,把它们按"失败的三种沉默形态"归类,最后给出可以直接抄进自家流水线的防御动作。所有案例都来自公开 issue 与 PR,涉及的行为分析限于对公开材料的阅读,没有对任何生产系统做测试。
形态一:静默丢失——东西没了,一切照常
Claude Code 在盘符根目录不加载 CLAUDE.md。 一位 Windows 用户在 subst U: D:\repo 之后从 U:\ 启动 Claude Code,发现项目记忆文件凭空消失。他不但报了 bug,还把根因挖了出来:项目记忆的发现逻辑遍历当前工作目录的祖先路径,但循环条件写的是"遇到文件系统根就停"——当 cwd 本身就是根时,循环体一次都不执行,祖先集合为空,U:\CLAUDE.md 和 U:\.claude\CLAUDE.md 根本没被看过一眼。最阴的地方在于同目录下的 settings.json、skills、commands、hooks 全部正常加载(它们直接对着 cwd 解析),唯独记忆缺失。用户的直观感受是"我写的规则模型就是不遵守",很难往加载器上想。issue 里给出的修复建议是把祖先遍历改成包含根节点。POSIX 下把仓库直接放在 / 也有同样的形状。
Copilot CLI 把 MCP 返回的图片悄悄扔掉。 issue #4600 的报告者做了教科书级的变量隔离:同一脚本、同一 MCP 服务器、同一工具、同一问题、同一台机器,只换 provider——默认 Copilot provider 下图片正常到达模型,换成 BYOK(自带密钥接 Azure)后模型开始"看图说话"地编造描述。没有任何报错、没有警告、没有空响应,像素就是到不了模型。报告者还补了一个对照:同样的 MCP 调用经 Python SDK 自定义工具代理,转换产物是格式完好的 image 块(mime_type、base64 都对),说明丢失发生在 SDK 之下的请求组装层。他特别点出这类 bug 为什么能混进发布:如果你的测试返回一张可预测的图(红色方块、固定顺序的色块),模型会编出一个"看起来对"的描述,测试反而通过。这条值得每个做多模态回归的团队抄下来:测试图必须包含不可编造的信息(随机字符串、时间戳、本次运行才存在的数字),否则你测的是模型的想象力而不是管道。
形态二:状态撒谎——失败了,但汇报成功
Gemini CLI 的子代理把回合耗尽说成目标达成。 issue #22323 附了完整的会话日志:主代理调用 codebase_investigator 子代理去分析两个本地仓库,工具结果的外层写着 "status": "success"、Termination Reason: GOAL;而同一条结果的内层文本说"调查被系统强制终止(超过最大回合数),未执行任何代码库分析",RelevantLocations 是空的。外层元数据和内层载荷自相矛盾,上层代理只能看到 success。报告者的推断有二:回合预算状态泄漏进了新的子代理调用,或者子代理的限制/错误状态被错误映射成了 GOAL 终止。他还补充了一个观察:失败发生后主代理转入手动文件探索、最终产出了实现计划——整体对话没有崩,但专门的分析工具确实没干活。
对做多代理编排的人来说这是核心级警告:你的编排层信任的 status 字段,可能只是子代理生命周期代码的一个映射 bug。 只要父代理依据 success 继续往下走,下游每一步都建立在虚构的分析之上。便宜的缓解手段是让父代理校验产物而不是状态位——比如要求子代理必须列出至少一个具体文件路径才能算成功,空列表一律视为失败重跑。
Gemini CLI 的通用代理还会无声挂起。 issue #21409 里,简单到创建文件夹的操作也能让 generalist agent 挂住长达一小时;加 --debug 也拿不到根因线索。有意思的是报告者找到了绕行方法:指示模型不要委派给子代理,任务立刻完成。挂起点大概率在委派链路上,但产品层面没有任何超时提示,用户唯一能做的是一小时后手动取消。
形态三:会话砖化——一次坏输入,永久毒历史
Claude Code 的 Read 盲信扩展名。 issue #102493 的复现路径出人意料地日常:PyMuPDF 的 Document.save('x.png') 会按字面写出一个 PDF,文件头是 %PDF-1.7。用 Read 读这个 x.png,工具输出"Read image (N KB)",不做任何字节校验就把 media_type 标成 image/png 塞进消息历史。API 随后拒绝这个非法图片返回 400,而由于坏附件存在于历史里、每轮都会重发,从此这个会话的每一条消息都失败。报告者列出了恢复的死路:/continue 重试的是同一份内存请求;错误信息只给 messages.N 这样的数组下标,不告诉你是哪个文件闯的祸;唯一的出路是手工编辑磁盘上的会话 JSONL、替换掉坏的 image 块再 --resume。他提的两条修复都很朴素:发射图片块前先嗅探魔数(PNG/JPEG/WEBP/GIF 各有签名),以及让错误点名肇事文件并提供"丢弃最后一个附件"的出口。
这个案例的结构价值超出 Claude Code 本身:凡是"历史重放"架构的产品(几乎所有对话式 agent),单点脏数据都会被放大成永久性故障。 上下文压缩、会话恢复、分支 fork 这些功能越是把历史当资产,写入路径的校验就越不能省。
上游已经在修,修法比修了什么更重要
翻当天的合入记录,能看到厂商对这些问题并非无感,而且修复思路出现了收敛:
Codex 在 10 月底合入的 PR #57848 给 Code Mode 的输出收集加了界:文本保留前缀加滚动尾部、上限 1 MiB,图片 URL 独立配额 8 MiB、音频 62 MiB,外加 32,768 条项数上限;渲染时用共享的 token 预算格式化器,并且即使预算为零也要报告收集器的丢弃情况。最后这半句正是"静默"的反面教材——丢可以,丢了必须说。要如实交代一个边界:PR 描述写明这一步只是把输出收集与渲染组件放进 code-mode-runtime,尚未接入运行时执行——限额生效还要等后续接线,读者不应以为当前版本已经在报丢弃量。
Claude Code 社区侧有个方向相同的插件 PR(#32755,edit-verifier):用 PostToolUse hook 在每次 Edit 之后验证编辑是否真的生效,专治"编辑静默失败后模型基于错误假设继续干活"。Codex 的有界输出、Claude 的编辑后验证、Gemini issue 里"把限制显式报为失败"的诉求,指向同一件事:把原本吞掉的失败信号显式化。
另一个跨产品的观察是平台兼容性的问题密度:本文核实的案例里,Claude Code 的两个报告都发生在 Windows——盘符根目录 bug 源自 subst 映射驱动器,会话砖化环境是 Windows 11 Pro;Gemini CLI 的 GOAL 谎报复现于 Windows + PowerShell 5.1。挂起那个案例反而在 Linux 上。Windows 路径语义(盘符、大小写、映射驱动器)与 POSIX 的差异会直接改变 agent 工具链的行为分支,重度 Windows 用户选型时值得把"该平台上的可靠性记录"当作一条独立评估维度,而不只看功能清单。
给你的流水线加三道闸
把上面的案例倒过来读,就是防守清单:
第一道,产物校验代替状态校验。不要只看子代理或工具返回的 status 字段,检查它的实际产物:文件是否存在、diff 是否非空、列表是否真的有内容。Gemini 的 GOAL 谎言和 Claude 社区插件针对的静默编辑失败都能被这一道拦住。
第二道,给不可编造的测试样本。多模态和长链路测试里放本次运行才生成的随机信息(时间戳、随机串),断言输出里必须原样出现。Copilot #4600 那种"图片没到但描述很像"的通过是假通过。
第三道,写入即校验,失败要可见。文件进上下文前验魔数,超限截断时打印丢弃量。issue 里的建议修复(Read 先嗅探魔数)和 Codex PR 的设计(零预算仍报损失)全是这个原则的具体化。
还有一条习惯性的:重要工作流固定在经过验证的版本上再择机跟进。这批案例里能看到版本敏感的影子——Claude Code 的会话砖化报告绑定在 v2.1.89 环境,盘符根目录 bug 是路径解析逻辑的当前形状。工具迭代快的时候,行为回归窗口比你想象的短,选型评估看可靠性记录优先于功能清单。
参考来源(均为 2026-10-29 采集快照):anthropics/claude-code issues #80202、#102493;github/copilot-cli issue #4600;google-gemini/gemini-cli issues #22323、#21409;openai/codex PR #57848。