Codex 报 remote compaction v2 expected exactly one compaction output item 怎么办
Codex 对话聊长了会自动压缩上下文,你也可以手动敲 /compact。有时候压缩失败,报的是这么一长串:
Error running remote compact task: Fatal error: remote compaction v2 expected exactly one compaction output item, got 0 from 3 output items
最后那两个数字每个人不一样,可能是 got 0 from 1,也可能是 got 2 from 5。
这条和常见的 Error running remote compact task: stream disconnected... 不是一回事:后者是网络断了,这条是网络没断、服务端也回话了,但回的内容不对。所以换网络、多等一会儿,对它基本没用。
本文依据 Codex 仓库 rust-v0.157.1(2026-09-25 发布)的源码。
一、「remote compaction v2」是什么
Codex 压缩上下文有两种做法:
- 远程压缩:把对话发给服务端,由服务端生成一个压缩结果,Codex 拿回来替换掉原来的长历史;
- 本地压缩:Codex 自己拿一段提示词让模型写一份总结,用总结替换历史。
用哪一种,源码里是按提供方决定的:走远程压缩(当前实现叫 v2)的有三类:Codex 内置的 OpenAI 提供方、Azure 的 Responses 端点、Amazon Bedrock;其它提供方走本地压缩。注意 Azure 的判定不只看名字,也看 base_url 里有没有 Azure 的标记,所以地址里带这类标记的中转站同样会走远程压缩。
这也解释了为什么报错开头总是 Error running remote compact task:Codex 只在远程压缩失败时才给错误加这个前缀。
二、这条错误的判定逻辑
远程压缩的请求发出去以后,服务端会像普通回答一样流式返回若干个「输出项」。Codex 在流里数其中类型是 compaction 的项有几个。
流正常结束后,源码的要求是:压缩项必须恰好 1 个。
- 0 个:服务端回了东西,但里面没有压缩结果;
- 2 个及以上:结果有歧义,Codex 不知道该用哪个。
两种情况都会抛这条错误,并把「压缩项数量」和「总输出项数量」写进去,就是你看到的 got X from Y。
它的错误类型是 Fatal,属于终止型错误,不会自动重试。这和网络类的流中断不一样,后者 Codex 会先自己重连几次。
三、为什么接中转站的人特别容易撞上
注意第一节里那个判断条件:是不是「内置的 OpenAI 提供方」,看的是提供方的名字,不看地址。
很多人接中转站的方式,是在 config.toml 里设 openai_base_url,把内置 OpenAI 提供方的地址换成中转站。这样做,Codex 仍然认为自己在跟 OpenAI 说话,照样走远程压缩,压缩请求就发给了中转站。
而远程压缩要求服务端返回一个特定类型的压缩项。中转站如果只是把请求转给别的模型、或者没有原样透传这类输出项,返回结果里压缩项就是 0 个,于是就是 got 0 from N。
所以如果你满足下面两条,基本可以锁定原因:
- 设置了
openai_base_url,指向的不是 OpenAI 官方; - 这条错误每次压缩都出现,而不是偶尔一次。
四、怎么处理
如果只是偶尔一次:再敲一次 /compact。源码里确实有一个「换模型再试」的兜底,但范围很窄:只在你切换了模型、Codex 先用上一个模型做压缩的场景下才有,而且要求走 ChatGPT 后端、提供方是内置 OpenAI、新旧模型不同。常规的自动压缩和手动 /compact 都没有这个兜底,失败了就直接报出来。
如果每次都失败,而且你用的是中转站:让 Codex 别把它当成 OpenAI。做法是不用 openai_base_url,改成自定义一个提供方,name 不要写成内置提供方的名字 OpenAI:
model_provider = "myrelay"
[model_providers.myrelay]
name = "myrelay"
base_url = "https://你的中转站/v1"
env_key = "MYRELAY_API_KEY"
wire_api = "responses"
按第一节的规则,名字不是 OpenAI 的提供方会走本地压缩,不再向中转站要那个压缩项,这条错误自然就不会出现了。
改之前要清楚两点代价:
- 本地压缩是让模型自己写总结,和服务端的远程压缩效果不一定一样;
- 自定义提供方默认不走 WebSocket,也不使用 ChatGPT 登录,key 从
env_key指定的环境变量里读。
如果你连的就是 OpenAI 官方:这条错误说明服务端返回有异常,先升级 Codex 到最新版再试;还不行,新开一个会话继续干活,原会话留着备查。
附:为什么非得「恰好一个」
这要从远程压缩成功之后 Codex 做了什么说起。按源码,压缩成功后,新的对话历史是这样拼出来的:
- 从原来的长历史里,按规则挑出一部分需要原样保留的消息,总量上限大约 64,000 token,超出的部分会被截掉;
- 在这些保留消息的后面,接上服务端返回的那一个压缩项。
压缩项就是被压缩掉的那一大段历史的替身。没有它,新历史就缺了中间一大块,模型接下来会不知道前面发生过什么;有两个,Codex 不知道哪个才是对的。与其拼出一份有问题的历史继续干活,不如直接停下来报错,这就是它被设计成终止型错误的原因。
附:它的「网络版」兄弟
同一个文件里还有一条长得很像的:
remote compaction v2 stream closed before response.completed
这条的意思是压缩请求的流在服务端说「完成」之前就断了,属于网络问题,和本文这条性质完全不同。它会自动重试,但压缩的重试次数被单独压得很低:取你配置的流重试次数和 2 之间较小的那个,也就是默认最多重试 2 次,而普通对话默认是 5 次。源码注释给的理由是:压缩请求本来就比普通回合跑得久,重试太多次会让人等太久。
所以在压缩时碰到断流,它比普通对话更容易直接失败。这种情况按网络问题处理,见相关阅读里的 remote compact task 那一篇。
五、一句话总结
got 0 from N:服务端回话了,但没有压缩结果;- 不会自动重试,换网络也没用;
- 用
openai_base_url接中转站又每次都报:改成自定义提供方,让 Codex 走本地压缩。
相关阅读
留言讨论
评论发布后会被人工复核,违规内容将被删除。
如果发表没有反应,可以前往联系我们告诉我们。