看到 Codex Desktop 连续显示 Reconnecting... 1/5、2/5 时,最自然的反应是怀疑网络或代理。但这个提示只说明当前传输没有顺利完成,还没有告诉我们故障发生在 DNS、代理入口、TLS、WebSocket、HTTP,还是认证层。
Reconnecting 是界面状态,不是根因;真正决定排查方向的是它后面的完整错误文本。
本文来自一次 Windows 11 上的实际故障:访问目标是 chatgpt.com,返回证书却属于另一组域名;修复代理入口后,症状又变成 Responses WebSocket 在完成事件之前关闭。两段故障都表现为 Reconnecting,处理方法却完全不同。
文中的公开配置和环境变量已按 2026 年 8 月 23 日的 OpenAI Codex 文档重新核验。Codex 的传输实现仍会变化,涉及内部端点、回退时机和 Desktop 日志位置的内容应以当前版本和最新日志为准。
先把 Reconnecting 拆成四层
排查前先保存四类证据:目标域名、证书错误全文、日志中的传输名称,以及请求在重试后是否最终成功。它们可以把相似的界面现象分到不同层级。
| 层级 | 常见线索 | 优先检查 |
|---|---|---|
| 路由与证书身份 | certificate not valid for name,证书属于其他域名 | 代理端口、代理协议、DNS、SNI、出口规则 |
| TLS 信任链 | unknown issuer、BadSignature、私有根证书不受信任 | 企业 CA、证书算法、Codex CA 配置 |
| Responses WebSocket | responses_websocket、固定次数重试、随后 HTTP 成功 | 代理能否维持 WebSocket、回退行为 |
| HTTP 与认证 | HTTP 同样失败、401、持续超时 | 登录状态、凭证、服务状态和基础连通性 |
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 22, "curve": "linear", "wrappingWidth": 150}, "themeVariables": {"fontSize": "13px"}}}%%
flowchart LR
A["读取完整错误与最新日志"] --> B{"证书名称匹配?"}
B -->|"否"| C["修正代理、DNS、SNI 与路由"]
B -->|"是"| D{"证书链受信任?"}
D -->|"否"| E["检查企业 CA 与 Codex CA 配置"]
D -->|"是"| F{"仅 WebSocket 持续失败?"}
F -->|"是"| G["检查代理能力与 HTTP 回退"]
F -->|"否"| H["检查认证、服务状态与基础网络"]
同一个 Reconnecting 可以来自完全不同的层级。先完成分类,后面的每一步才有明确的验证目标。
第一层:证书属于另一个域名
这次故障最初返回的是:
stream disconnected before completion:
invalid peer certificate:
certificate not valid for name "chatgpt.com";
certificate is only valid for DnsName("*.example.net") ...
请求目标是 chatgpt.com,证书的 Subject Alternative Name 却只覆盖另一组域名。此时证书“是否受信任”还排在后面,首要问题是连接为何抵达了错误的 TLS 终点。
证书域名错配无法靠导入 CA 修好;必须先纠正代理、DNS、SNI 或出口路由。
查看 Windows 当前代理
在 PowerShell 中读取当前用户的代理设置:
Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' |
Select-Object ProxyEnable, ProxyServer, AutoConfigURL, AutoDetect
这一步只能证明 Windows 记录了哪个代理入口,不能证明端口正在监听,也不能证明 Codex 子进程确实继承了它。
确认端口和进程
netstat -ano | Select-String -Pattern ':<代理端口>'
Get-Process | Where-Object {
$_.ProcessName -match 'clash|mihomo|v2ray|sing|verge|nekoray|hiddify'
} | Select-Object Id, ProcessName, Path
如果端口处于 LISTENING,再用 PID 和进程路径确认监听者确实是预期的代理核心。系统代理、PAC、TUN 和环境变量可能同时存在;只看其中一个界面,很容易把“已经配置”误当成“实际生效”。
确认端口协议
Clash 或 Mihomo 的 mixed-port 通常同时接受 HTTP 与 SOCKS 请求。若配置中同时存在 mixed-port、port 和 socks-port,应先确认自己使用的是哪一个入口,再决定写成 http://127.0.0.1:<端口> 还是 socks5://127.0.0.1:<端口>。
保持代理节点不变,只修正端口或协议,可以减少额外变量。若同时换节点、改 DNS、切 TUN 并重启客户端,即使问题消失,也很难知道哪一步真正有效。
第二层:域名正确,但证书链不受信任
另一类 invalid peer certificate 会显示正确的目标域名,却因为企业 TLS 检查、私有根证书或证书算法策略而失败。当前 OpenAI Codex 环境变量文档列出了:
CODEX_CA_CERTIFICATE:指向 PEM CA bundle,供 HTTPS、登录和 WebSocket 客户端使用;SSL_CERT_FILE:当CODEX_CA_CERTIFICATE未设置时使用的后备 CA bundle。
在受管理的企业网络中,应由网络或安全管理员提供正确的 PEM 证书链。Windows 用户可以把路径设置为用户级环境变量:
[Environment]::SetEnvironmentVariable(
'CODEX_CA_CERTIFICATE',
'C:\path\to\corporate-ca-bundle.pem',
'User'
)
设置后需要完全退出 Codex Desktop,包括托盘和后台进程,再重新启动。单独关闭窗口可能仍保留旧进程环境。
这里有两条边界:
- CA bundle 只处理信任链,无法修复证书名称属于其他域名的错误;
- 部分过旧或弱算法证书仍可能被 TLS 库拒绝,把它加入 bundle 也不一定足够。
不要通过关闭证书验证来换取“能连上”。那会让代理错配、流量劫持和真实的证书问题失去最后一道可见信号。
第三层:HTTPS 可用,WebSocket 持续重试
修正代理入口后,故障可能变成:
stream disconnected before completion:
websocket closed by server before response.completed
此时需要结合日志判断。若日志满足下面的模式,问题已经从 TLS 入口移动到传输层:
- 证书域名错配不再出现;
- 普通 HTTPS 或后续 HTTP 回退能够完成;
- 日志明确显示
responses_websocket; - 每次新任务以近似固定间隔重试;
- 重试耗尽后,HTTP/SSE 可以继续返回结果。
openai/codex 的公开 issue 中有多份类似记录:WebSocket 连接失败后,客户端用完流式重试预算才进入 HTTP 回退。它们能证明这种故障模式确实存在,但 issue 中的具体超时长度、版本和 workaround 都属于当时环境,不应当作永久行为。
只有在“HTTPS 已经可用、失败稳定落在 WebSocket、HTTP 回退能够成功”三项都成立后,才有理由讨论传输层规避。
先检查代理能否承载 WebSocket
优先做低风险检查:
- 确认 Codex 与代理端口之间存在新建立的连接;
- 检查代理规则是否单独处理
chatgpt.com或 WebSocket; - 在代理的 HTTP、mixed-port、TUN 模式之间做单变量对照;
- 每次只改变一个入口,并保存对应时间段的日志;
- 问题能够稳定复现时,再比较新任务和已预热任务。
系统代理显示正确并不代表 Codex 的所有网络进程都会继承同一份设置。CLI、Desktop app-server、Electron 界面和 WSL 运行时可能位于不同进程边界,验证时要说明实际测试的是哪一个入口。
HTTP-only Provider 只能作为条件性实验
当前官方配置参考仍包含 model_providers.<id>.supports_websockets,含义是“这个 Provider 是否支持 Responses API WebSocket 传输”。公开 issue 曾用自定义 Provider 将其设为 false,从而绕过重复的 WebSocket 连接尝试。
这不等于 Codex 提供了一个稳定、通用的 prefer_http 开关。自定义 Provider 还会引入新的变量:内部 base_url 可能变化,ChatGPT 登录授权未必适用于该端点,任务列表也可能按照 Provider 发生可见性分区。
config.toml 与任务状态,并确认自己能原样回滚;历史暂时不出现在列表里,不等于历史已经被删除。
因此,本文不把公开 issue 中的内部端点配置包装成默认复制粘贴方案。确实需要验证时,应先阅读任务存储与生命周期模型和历史任务不可见诊断方法,在隔离副本中完成单变量实验,再决定是否改变日常配置。
第四层:HTTP 也没有成功
如果 WebSocket 失败后,HTTP 请求同样超时或返回 401,继续调整 WebSocket 没有意义。此时应分别检查:
- Codex 登录状态与认证模式;
- 代理是否允许目标域名的普通 HTTPS;
- DNS、TLS 与系统时间;
- OpenAI 服务状态;
- 当前网络是否存在企业拦截、出口限制或私有 CA;
- 新日志中最早出现的底层错误。
不要从最后一行“stream disconnected”倒推全部根因。更早的证书、连接或认证错误通常更接近故障起点。
怎样获得可用的日志
当前官方文档建议在需要明文诊断日志时显式设置 log_dir,并用 RUST_LOG 控制详细程度:
$env:RUST_LOG = 'codex_core=debug,codex_tui=debug'
codex -c 'log_dir=.\.codex-log'
非交互模式的 codex exec 会直接在终端输出消息,也适合比较同一条最小请求在不同配置下的行为。Desktop 的日志目录可能随安装方式和版本变化;应按修改时间定位最新文件,不要依赖一条永久不变的绝对路径。
收集日志时至少记录:
- Codex 与 Desktop 版本;
- Windows 与运行入口;
- 认证模式;
- 代理模式和端口类型;
- 第一次失败的完整错误;
- 是否出现
responses_websocket; - 是否出现 HTTP 回退,以及回退后是否成功;
- 配置修改和完全重启的时间。
日志可能包含请求地址、目录、任务 ID,甚至嵌入 URL 的凭证。公开之前应逐项脱敏。
一套低风险排查顺序
把上面的步骤压缩后,可以按这个顺序执行:
- 保存完整错误、版本和最新日志,不先改配置。
- 检查证书名称是否属于目标域名。
- 核对系统代理、实际监听端口、进程和端口协议。
- 域名正确但链不受信任时,再检查企业 CA 与
CODEX_CA_CERTIFICATE。 - HTTPS 成功后,确认剩余故障是否稳定落在 Responses WebSocket。
- 用代理入口或模式做单变量 A/B,观察 HTTP 回退是否成功。
- 只有前述证据齐全时,才评估 HTTP-only Provider,并准备完整回滚。
- 完全退出 Codex 后重启,用新任务和新日志验证。
每次只改变一个变量,并让每次改动回答一个明确问题;排障速度往往取决于变量控制,而不是命令数量。
怎样判断已经修好
一次请求成功还不够。至少完成下面几项回归检查:
| 检查 | 通过标准 |
|---|---|
| TLS | 完全重启后不再产生新的域名错配或信任错误 |
| 新任务 | 第一条消息能够完成,不再固定次数重试 |
| 现有任务 | 可以继续读取和发送消息 |
| Provider | 任务列表没有因配置切换产生未解释的分区 |
| 日志 | 最后一次成功请求之后没有新的同类错误 |
| 回滚 | 可以恢复原配置并得到可解释的对照结果 |
旧日志仍会保留修复前的关键词。判断当前状态时,应以配置变更后的时间戳和新请求为界,不要因为全目录搜索还能找到旧错误,就把已经修好的问题重新判成失败。
评论
由 GitHub Discussions 提供支持。