Codex 每次 Reconnecting 5 次才回答的解决方案

有一种 Codex 卡顿很有迷惑性:你发出第一句话,它不马上回答,而是在那儿一遍遍显示 Reconnecting...。等你以为它要挂了,它又突然开始正常输出。

Reconnecting... 1/5
Reconnecting... 2/5
Reconnecting... 3/5
Reconnecting... 4/5
Reconnecting... 5/5

这种体验很烦。它不像彻底断网,也不像账号失效,因为最后确实能用。真正浪费时间的,是前面那几十秒到一分多钟的空等。

我查了 openai/codex 官方仓库里的相关 issue。结论比较明确:不少类似案例都指向同一个地方,Codex 先走 WebSocket,失败几次后才退回 HTTP/SSE。

这不是玄学,日志里能看到

在 openai/codex #22634 里,有用户记录了一个很典型的现象:Windows 上每次新会话的第一条消息都会等大约 75 秒。不是模型一直在想,而是客户端在反复尝试 WebSocket。

那条 issue 里的判断是:Codex 先用 responses_websocket,一次大约 15 秒,失败 5 次,之后才切到 HTTP。切过去以后,同一条请求很快就完成了。

#19821 也提到类似问题:WebSocket 连接阶段的 timeout / network error 被当成普通 stream failure 处理,导致它要耗完整个 retry 预算,才启用 HTTP fallback。换句话说,Codex 不是不知道 HTTP 能用,只是切得太晚。

还有一些 Desktop 相关反馈也很接近。比如 #24045 里,用户在代理环境下看到新线程或恢复线程时的 reconnect loop;日志显示它先尝试 wss://chatgpt.com/backend-api/codex/responses,失败后再用 responses_http 完成请求。#21880 则提到 Windows Desktop 新线程首次消息时的 websocket prewarm timeout。

为什么 HTTP 能用,WebSocket 却不行

很多代理软件对普通 HTTP/HTTPS 处理得很好,但 WebSocket 是另一回事。它走的是 wss://,有升级握手,有长连接,还会受代理规则、系统代理、TUN、公司网关、证书和服务端策略影响。

所以你会看到一个很怪的结果:浏览器能访问,普通 HTTPS 请求能过,Codex 最后也能回答,但 WebSocket 那条路就是不顺。

这也是为什么这个问题容易误判。用户看到的是“Codex 很慢”;日志里看到的却是 transport 在换路。

如果你想确认自己是不是这个情况,可以看 Codex 日志:

~/.codex/log/codex-tui.log

重点找这些词:

responses_websocket
stream disconnected
retrying sampling request
falling back to HTTP
responses_http

如果这些词连在一起出现,基本就不用继续怀疑模型了。问题多半在 WebSocket 传输层。

最省事的处理:让它别走 WebSocket

如果你只是想先把 Codex 用起来,最直接的办法是给 Codex 配一个 HTTP-only provider。核心不是换模型,而是告诉它:这个 provider 不支持 WebSocket,别再先试 responses_websocket。

配置文件位置一般是:

macOS / Linux: ~/.codex/config.toml
Windows: C:\Users\你的用户名\.codex\config.toml

如果你是 ChatGPT 登录模式,可以参考这个写法:

model_provider = "chatgpt-http"

[model_providers.chatgpt-http]
name = "ChatGPT HTTP"
base_url = "https://chatgpt.com/backend-api/codex"
wire_api = "responses"
requires_openai_auth = true
supports_websockets = false

保存后重启 Codex。真正起作用的是最后一行:

supports_websockets = false

这个 workaround 在公开 issue 里有人验证过。#22634 的反馈里,改成 HTTP-only provider 后,原本约 75 秒的首条消息等待降到了约 6 秒。

这里有个坑:不要只抄半段配置。ChatGPT 登录模式下,base_url 和 requires_openai_auth = true 最好写上。否则你以为禁用了 WebSocket,结果 provider 本身没配完整,又会引出新的问题。

如果你用的是 API Key,或者走自己的 OpenAI-compatible 网关,那就不要照抄 chatgpt.com/backend-api/codex。你要按自己的 base_url、鉴权方式和环境变量来配,只保留 supports_websockets = false 这个思路。

想保留 WebSocket,就先把代理说清楚

禁用 WebSocket 是快刀斩乱麻。但如果你希望 Codex 仍然用默认传输方式,那就要确保 Codex 进程真的吃到了代理环境变量。

macOS / Linux 终端里可以这样启动:

export HTTP_PROXY="http://127.0.0.1:7890"
export HTTPS_PROXY="http://127.0.0.1:7890"
export NO_PROXY="localhost,127.0.0.1,::1"
codex

Windows PowerShell 可以这样:

$env:HTTP_PROXY="http://127.0.0.1:10808"
$env:HTTPS_PROXY="http://127.0.0.1:10808"
$env:NO_PROXY="localhost,127.0.0.1,::1"
codex

端口别照抄。Clash 可能是 7890,Clash Verge Rev 也有人用 7897,v2rayN 常见是 10808。看你自己的代理软件。

我会避开一个不太稳的说法:直接在 ~/.codex/.env 里写代理就一定生效。至少我没有在 openai/codex 仓库里找到明确证据,证明 Codex 会固定加载这个文件。更可靠的表述是:Codex 启动时的进程环境里要有 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY。

CLI 从终端启动还好办。Desktop 就麻烦一点,因为桌面应用不一定继承你当前 shell 里的环境变量。这种情况下,HTTP-only provider 反而更干净。

一劳永逸的方案:打开 TUN 

TUN 模式也能解决一部分问题。它从虚拟网卡层面接管流量,不用指望某个进程自己读代理变量。WebSocket、HTTPS、插件请求,都更容易被代理软件接住。

但它不是小改动。TUN 会影响整个系统的网络行为。公司内网、本地开发服务、Docker、数据库连接、某些桌面 App,都可能被它顺手改了路由。

所以我会把 TUN 放在最后。前面两个办法都不行,再开。不要为了修 Codex 一分钟卡顿,把系统网络搞成另一个排障现场。

我会怎么排查

如果是我遇到这个问题,会按这个顺序来:

先看 ~/.codex/log/codex-tui.log。只要看到 WebSocket 重试和 HTTP fallback,就不用再猜。

然后决定要不要保留 WebSocket。不在乎的话,直接配 supports_websockets = false,让 Codex 走 HTTP/SSE。

如果想保留 WebSocket,再去处理代理环境变量,确认 Codex 进程启动时真的拿到了 HTTP_PROXY 和 HTTPS_PROXY。

还不行,再考虑 TUN。

这个顺序的好处是不会把问题扩大。先修 Codex 自己的传输选择,再动系统网络。

几个容易写错的结论

第一,不要说“Codex 不能用”。更准确的是:WebSocket 路径失败,HTTP/SSE 路径还能用。

第二,不要把时间写死成 20 秒一次。公开反馈里有 15 秒一次、5 次约 75 秒的记录。不同版本、平台和网络环境可能不一样。

第三,~/.codex/.env 不是我会强推的方案。除非能证明当前版本会加载它,否则就写成“确保进程环境变量生效”。

第四,supports_websockets = false 是有效思路,但 provider 配置要完整。半截配置最容易让人误以为方案不对。

结论

Codex 开局反复 Reconnecting... 1/5 到 5/5,最后又能回答,这种情况不用先怀疑模型。公开 issue 里的证据更像是 WebSocket 连接失败后,客户端等完整个 retry 流程,才退回 HTTP/SSE。

想快点恢复,就配 HTTP-only provider,把 supports_websockets 设为 false。想保留默认能力,就先确认代理环境变量对 Codex 进程生效。TUN 留到最后。

这事说到底不是“Codex 慢”,而是它先走了一条在你当前网络里不太通的路。