排查 Codex CLI 的流式请求时,终端最后显示的错误未必就是最先发生的故障。一次 response.failed 已经携带明确的服务端错误,但只要 SSE 连接没有及时关闭,客户端仍可能继续等待,最终用 idle timeout 覆盖原始信息。要分清这两类故障,需要同时观察终止事件、连接状态与最终报错。
这篇不是转述 Issue,而是独立复现。测试使用本地 loopback HTTP SSE、独立 CODEX_HOME、800ms idle timeout、零重试,不调用模型和外部 API。
如果 Codex CLI 最后报的是:
stream disconnected before completion: idle timeout waiting for SSE
不要立刻把它理解成“服务端一直没有返回任何东西”。在 Codex CLI 0.153.4 和 0.154.0 上,我都独立复现了另一种情况:服务端已经通过 SSE 明确发送了 terminal response.failed,其中包含真实 server_error,但因为底层 socket 没有立即关闭,Codex 继续等待 EOF,直到 stream idle timeout 触发,最终把原始错误覆盖成了 idle timeout waiting for SSE。
这个区别很重要。生产环境里如果只保存 Codex 最后一条错误,你可能会把真正的 provider failure、容量拒绝或服务端错误误判成普通网络 idle timeout,然后去调 timeout、重试次数或代理配置,却没有处理最先到达的真实错误。
XBSTACK 在 2026 年 9 月 12 日用一个完全本地的 loopback HTTP SSE fixture 做了两组控制实验,不调用任何模型、不使用 OpenAI/Azure API Key,也不依赖外部网络。0.153.4 和当前稳定版 0.154.0 结果一致。
我们的测试矩阵如下:
| Codex CLI | 服务端行为 | 原始错误保留 | 是否出现 idle timeout |
|---|---|---|---|
0.153.4 | response.failed 后立即 EOF | 是 | 否 |
0.153.4 | response.failed 后 socket 保持打开 | 否 | 是 |
0.154.0 | response.failed 后立即 EOF | 是 | 否 |
0.154.0 | response.failed 后 socket 保持打开 | 否 | 是 |
0.153.4 的 open-socket 组耗时约 1.08 秒,0.154.0 约 1.12 秒。这里不是在做性能测试,因为 fixture 故意把 stream_idle_timeout_ms 缩短到 800ms,只是为了让行为快速出现。
真正需要看的是错误内容。
EOF 组:
stream disconnected before completion: XBSTACK_LOCAL_TERMINAL_FAILURE
open-socket 组:
stream disconnected before completion: idle timeout waiting for SSE
两组服务端发送的是同一个 response.failed。唯一差别是terminal event 发出后是否立即关闭连接。
fixture 返回:
HTTP/1.1 200 OK
Content-Type: text/event-stream
然后发送一个 terminal failure:
event: response.failed
data: {
"type": "response.failed",
"response": {
"status": "failed",
"error": {
"code": "server_error",
"message": "XBSTACK_LOCAL_TERMINAL_FAILURE"
}
}
}
第一组发送后马上关闭 socket;第二组发送完全相同的 event,但保持 socket 打开。
从协议语义看,response.failed 已经告诉客户端这个 response 进入 failed terminal state。客户端至少已经有足够信息把这次执行判定为失败。问题在于当前受影响路径没有在这里结束 SSE consumption,而是把错误暂存后继续等 transport EOF。
于是出现了这条链:
HTTP 200
↓
SSE response.failed
↓
Codex 已解析真实 server_error
↓
连接仍然保持打开
↓
继续等待 EOF
↓
stream idle timeout
↓
用户最终看到 idle timeout waiting for SSE
这不是“服务端什么都没返回”,而是服务端已经失败,客户端随后又被 transport timeout 覆盖了诊断信息。
假设真实 provider 返回的是:
server_error: backend capacity unavailable
但 socket 没有立即 EOF,Codex 最后只显示:
idle timeout waiting for SSE
如果只看最后一条,你很容易做这些动作:
stream_idle_timeout_ms 从 5 分钟调成 10 分钟;stream_max_retries;这些操作可能全部绕开了真正的 first failure。
调大 idle timeout 不是修复。 它只会把错误被覆盖的时间推迟。调小也不是修复,只会更早得到相同的 timeout 文案。
生产日志里至少同时保留四层信息:
response.failed.response.error;如果你看到类似:
HTTP 200
response.failed: server_error / <real message>
... socket remains open ...
Codex: idle timeout waiting for SSE
就高度符合本文复现路径。
但如果根本没有收到 response.failed,只有 socket 一直没有数据,那么那是另一类真正的 idle timeout,不能套本文结论。
XBSTACK 的公开 Repro 不依赖任何模型或 API 账号。核心只有一个 Python 标准库 HTTP server 和一个临时 CODEX_HOME。
执行:
python3 repro.py
--codex /Applications/ChatGPT.app/Contents/Resources/codex
--output logs/result.json
fixture 会自动跑:
Case A: response.failed -> EOF
Case B: response.failed -> keep socket open
并检查:
{
"original_error_preserved": true,
"idle_timeout_reported": false
}
和:
{
"original_error_preserved": false,
"idle_timeout_reported": true
}
为了避免污染真实 Codex 配置,每个 case 都新建临时 CODEX_HOME,并把 retry 设为 0。
上游 Issue #43140 最初针对的是 0.153.4,但 Codex 在 9 月 9 日已经发布 0.154.0。如果今天只重复 0.153.4,会有一个很明显的问题:用户不知道升级是否已经解决。
所以我额外下载 OpenAI Codex GitHub 官方稳定版 rust-v0.154.0 的 Apple Silicon 二进制,使用完全同一份 fixture 再跑一遍。
结果没有变化。
因此截至 2026 年 9 月 12 日,至少在本文控制条件下:
升级到 0.154.0 不能作为这个问题的已验证解决方案。
未来 0.155.x 或后续稳定版是否修复,需要重新跑 version matrix,不能从 main branch 或 alpha 版本推断稳定版行为。
Issue #43140 给出了源码层分析和一个候选 patch:核心思路是在已经识别出 response.failed terminal event 后直接结束 producer,而不是继续等待 SSE EOF。
这个方向与本文控制实验高度一致,但这里必须区分三件事:
目前我们只确认前两项。Issue 截至 9 月 12 日仍为 Open,因此本文不会写“升级到某个正式版本即可修复”。
如果使用自定义 provider、Azure OpenAI 或代理层,优先保留原始 SSE event。真正有诊断价值的通常是最先到达的 terminal failure,而不是几分钟之后的 timeout。
如果服务端错误里有 request id、region、capacity admission、retry-after 或其他错误分类,必须和 Codex run id 放在同一条 trace 里。
本文证明的是:
terminal response.failed + socket hold-open
这条路径可能覆盖原始错误。
它并没有证明:
所有 server_error
所有 no healthy upstream
所有 TLS failure
所有 Azure capacity failure
所有 Codex reconnect
都来自 Codex SSE consumer。
真实上游故障仍然可能存在。
如果你维护自己的 Codex build,可以研究上游 Issue 中的 proposed patch 和 regression tests。但生产团队不应把社区/Issue patch 等同于官方二进制更新。
更稳妥的发布流程是:
官方新 stable release
↓
运行本文 repro
↓
EOF case 保留原始 error
↓
open-socket case 也立即保留原始 error
↓
再确认修复
这篇文章本身是 reliability / observability 问题,不应该为了蹭“AI 安全”强行包装成安全文章。
但它和 Agent Security 有一个重要交叉点:如果执行层把真实失败覆盖成次级 timeout,审计、告警、自动恢复和风险分类都会失真。 对长运行 Agent 来说,“失败原因是否能被准确保留”本身就是 runtime control 与 incident response 的基础。
所以它应该从 AI Tools Lab / Codex 排障入口承接搜索流量,再内链到 AI Agent Security 的 Audit / Observability / Runtime Control,而不是反过来把这篇写成泛安全总览。
截至 2026 年 9 月 12 日,XBSTACK 独立确认:
0.153.4 受影响;0.154.0 仍受影响;response.failed 后立即 EOF 时,原始 error 可以正常保留;response.failed 后 socket 保持打开时,客户端会继续等 idle timeout;idle timeout waiting for SSE 可能覆盖真实 server failure;如果你正在排查 Codex 的 SSE reconnect,第一步应该不是继续调 timeout,而是先确认:在 timeout 之前,服务端是否已经发过 response.failed。
如果问题还涉及更广的 Responses 流式链路,可以继续对照 Responses API stream abort 后 tool call 丢失问题;如果你使用的是 Astra 或自定义 provider,再看 GPT-6 Astra API 实测与迁移边界。这类运行时排障统一收在 AI Tools Lab,而权限、审计和 Runtime Control 的安全边界则归到 AI Agent Security。
原文:www.xbstack.com/ai/tools-la…
相关阅读:
Windows10安装ubuntu18.04双系统教程的方法步骤(图文)
Debian系统怎么设置任务栏显示位置?
Codex Reasoning Effort 如何针对不同任务调节成本和速度?
Codex 的 low、medium、high、xhigh 推理强度分别适合哪些场景?
Ubuntu系统怎么禁止软件更新? 不升级指定软件的技巧
少调模型多走代码:解析 resolve-harness 的确定性 Fast Path 运行时