Codex 报 `workspace routing discovery timed out`:代理冲突与残留进程排查
一次 Codex CLI 启动超时的排查记录:从 CONNECT 成功但 TLS 失败、独立 CODEX_HOME 对照,到统一代理路径并重启残留进程后恢复。
一次 Codex CLI 升级后,Ubuntu 上的 TUI 在启动阶段持续报 workspace routing discovery timed out。最初看起来像版本回归,实际排查却指向两组相互叠加的问题:旧 app-server 仍带着启动时的网络环境运行,同时 shell 中又混用了 HTTP 代理和 SOCKS5 ALL_PROXY。
这篇文章保留真正帮助缩小范围的证据:为什么 HTTP CONNECT 成功不等于 HTTPS 可用,怎样用独立 CODEX_HOME 区分账号、配置与网络问题,以及如何在不删除原有 Codex 数据的前提下验证修复。
症状与环境:先不要把升级当成根因
当时 Codex 在 TUI 启动阶段失败:
Error: account/read failed during TUI bootstrap:
account/read failed:
workspace routing discovery timed out
(code -32603)
故障恰好出现在升级之后,因此首先怀疑的是 CLI 版本、登录状态和 workspace 路由。但降级后问题没有立即消失,这已经说明“升级导致回归”只是时间上的相关性,不能直接当作根因。
系统同时设置了两类代理。下面用占位端口表示当时的本机配置:
export http_proxy=http://127.0.0.1:<HTTP_PROXY_PORT>
export https_proxy=http://127.0.0.1:<HTTP_PROXY_PORT>
export all_proxy=socks5://127.0.0.1:<SOCKS_PROXY_PORT>
这种配置不一定会让所有程序失败,但不同请求实现对代理环境变量的支持和优先级可能不同。排查重点因此不应只是“有没有代理”,而是“失败的那类请求实际走了哪条路径”。
关键网络证据:CONNECT 成功不等于 TLS 成功
先显式指定 HTTP 代理访问 ChatGPT,CONNECT、TLS 和证书验证都能完成。服务端返回的 HTTP 状态不是这一步的重点;重要的是代理隧道与 TLS 已经建立:
curl -Iv --proxy http://127.0.0.1:<HTTP_PROXY_PORT> https://chatgpt.com
HTTP/1.1 200 Connection established
SSL connection using TLSv1.3
SSL certificate verify ok
但让 curl 直接读取环境变量访问 OpenAI 和 ChatGPT 后端时,结果并不稳定。其中一次请求先收到代理的 CONNECT 成功,再在发出 TLS ClientHello 后失败,而且耗时稳定在约 5 秒:
curl -Iv https://chatgpt.com/backend-api/wham/accounts/check
HTTP/1.1 200 Connection established
OpenSSL SSL_connect: SSL_ERROR_SYSCALL
这组现象把范围从“完全无法联网”缩小为“代理隧道之后的 TLS 路径异常”。它也解释了为什么 Codex 的上层错误看起来像 workspace 路由超时:底层 HTTPS 请求没有完成,路由发现自然无法按时返回。
只成功一次仍不足以证明链路稳定。可以重复请求并记录连接、TLS 和总耗时:
for i in {1..10}; do
curl -sS -o /dev/null \
--connect-timeout 10 \
-w 'code=%{http_code} connect=%{time_connect} tls=%{time_appconnect} total=%{time_total}\n' \
https://chatgpt.com/backend-api/wham/accounts/check \
|| echo FAILED
done
如果同一目标交替出现成功和 FAILED,应继续检查代理选择和上游线路,而不是用一次成功覆盖间歇故障。
隔离实验:把账号、配置和常驻进程拆开
当时版本的 codex doctor 暴露了两个线索。第一,磁盘上的 CLI 已经降级,仍在运行的 app-server 却保持升级后的版本。第二,shell 可以看到代理变量,而旧 app-server 的诊断结果仍显示没有代理环境。常驻进程只继承启动时的环境,后来修改 shell 配置不会自动刷新它。
为了判断原有 CODEX_HOME 中的配置或状态是否损坏,我创建了权限为 700 的独立目录,并让 Codex 在其中完成独立登录和启动:
install -d -m 700 "$HOME/codex-clean"
CODEX_HOME="$HOME/codex-clean" codex
新环境能够进入 TUI;当时的诊断还显示代理变量已被读取,WebSocket 与后端连通性正常。这个对照实验说明账号和当前 CLI 并非必然失败,问题更可能集中在原有环境、残留进程和代理组合上。
这里没有复制或展示原目录中的认证文件。隔离实验如果需要登录,应让 Codex 自己完成认证,并继续保护目录权限。也不要为了排查直接删除 ~/.codex;其中可能包含配置、会话状态和历史记录,删除会扩大损失,却未必触及网络根因。
根因与修复:统一代理路径,再重启旧进程
停止残留进程后,原环境可以重新启动,但诊断呈现出更有辨识度的组合:网络变量已读取、WebSocket 成功,而 HTTP reachability 仍在 TLS 阶段失败。结合前面的显式代理测试,最终把差异收敛到同时存在的 HTTP 代理与 SOCKS5 all_proxy。
本次环境中的有效修复是移除全局 SOCKS5 配置,只保留统一的 HTTP/HTTPS 代理:
export HTTP_PROXY=http://127.0.0.1:<HTTP_PROXY_PORT>
export HTTPS_PROXY=http://127.0.0.1:<HTTP_PROXY_PORT>
export http_proxy=http://127.0.0.1:<HTTP_PROXY_PORT>
export https_proxy=http://127.0.0.1:<HTTP_PROXY_PORT>
unset ALL_PROXY
unset all_proxy
修改环境变量后还要让旧 app-server 退出,否则它仍会使用启动时的环境。先保存正在进行的工作,再定位并终止确认属于本次会话的进程:
pgrep -af codex
kill <CONFIRMED_CODEX_PID>
直接使用 pkill -f codex 会终止当前用户的所有匹配进程,包括其他 Codex 会话;只有确认影响范围并接受这些会话被中断时才应使用。
完成代理统一并重新启动后,当时的网络、WebSocket 和 reachability 检查均恢复,TUI 也能正常进入。至于不同 Codex 请求是否确实由不同网络实现选择了不同代理变量,本次没有通过源码跟踪或抓包完全证明;这是与现象一致的解释,而不是可推广到所有版本的实现结论。
可复用的排查顺序
同类错误适合按“进程状态—代理环境—网络分层—隔离对照”的顺序处理:
- 记录 CLI 版本,并检查是否仍有旧 Codex 或 app-server 进程。升级、降级或修改代理后,磁盘版本与常驻进程状态可能不一致。
- 检查大小写代理变量,特别关注是否同时存在
HTTP_PROXY、HTTPS_PROXY和ALL_PROXY。输出变量名和代理类型即可,不要公开带凭据的代理 URL。 - 分开验证 TCP/CONNECT 与 TLS。看到
200 Connection established只能证明代理接受了隧道请求,不能证明目标站点的 TLS 握手已经完成。 - 分开验证 WebSocket 与普通 HTTPS。一个成功、另一个失败时,优先检查代理支持、变量选择和中间网络设备。
- 连续测试多次,记录
time_connect、time_appconnect和总耗时,避免把间歇成功误判成稳定恢复。 - 使用独立
CODEX_HOME做对照,不要先删除原目录。新环境正常时,再回头检查旧进程、旧配置和状态文件。 - 每次只改变一个变量:先统一代理,再重启进程,再复测。否则无法判断是哪项操作真正改变了结果。
还应检查可能影响 TLS 的 SSL_CERT_FILE、SSL_CERT_DIR 和 NODE_EXTRA_CA_CERTS。如果统一代理后仍失败,就继续调查 CA、DNS、防火墙、VPN 和代理上游,而不是反复登录账号。
验证结果与适用边界
本次排查最终完成了三层验证:显式 HTTP 代理下的 TLS 握手能够成功;移除 ALL_PROXY 并重新启动进程后,Codex 当时提供的网络诊断全部恢复;原项目目录中的 TUI 可以再次进入。
这只能证明该 Ubuntu、Codex CLI 和本地代理组合中的故障已经解除。workspace routing discovery timed out 是上层超时表现,账号状态、服务端故障、DNS、证书链或防火墙也可能产生相似结果。codex doctor 的命令、字段和输出还可能随版本变化,应以当前安装版本实际提供的诊断能力为准。
最值得保留的经验不是“看到这个错误就删除 ALL_PROXY”,而是先验证请求在哪一层停止,再用隔离环境和单变量实验缩小范围。只有修复前后的网络证据一致,才能把一次时间上的巧合变成可复查的排障结论。