Codex首包卡死问题调查与解法
速览
本文分析了Codex模型在HTTP/SSE模式下因响应头阻塞导致首包卡死的问题,指出OpenAI服务器可能延迟发送200状态码,并给出了四种优先级解法:推荐使用OAuth登录或WebSocket模式;其次通过配置websockets和first-event-timeout等参数实现超时重连。文章还解释了两种请求模式的差异,以及如何通过二次开发修改代理解决。
AI 深度解读
背景
近期不少 Codex 用户遇到一个棘手问题:模型在 "thinking" 阶段卡死,没有任何报错,界面看起来完全失去响应。使用某些反向代理(如 5.6 版本)时,模型只要执行完工具调用就会卡住;手动停止再继续则可以正常。这一问题严重影响了工作流效率,社区在 LINUX DO 上展开了深入调查,最终定位到 OpenAI API 的 HTTP/SSE 请求模式中的 "首包卡死" 现象,并给出了多种解法。
核心内容
问题现象
- Codex 卡死在 "thinking" 状态,无报错,界面完全冻结。
- 使用特定反代版本(如 5.6)时,模型执行完工具调用后卡住,但停止并重新继续对话即可恢复。
- 本质是 OpenAI 收到请求后迟迟不返回任何响应数据。
请求模式对比
HTTP/SSE 模式(逐请求)
每一次模型调用都是一次全新的独立请求:重新建立 TCP 和 TLS 连接,把整段上下文发送给 OpenAI,等待响应头(首包),然后以 SSE 流式逐事件返回。下一次调用重复全部流程。
关键步骤(以 CPA 为例):
- TCP 三次握手
- TLS 握手(uTLS 伪装 Chrome 指纹)
- 发送 HTTP POST 请求(含完整上下文,可达 ~90K token)
- 等待响应头 —— 卡死点:OpenAI 已受理请求但后端迟迟不开始产出字节,健康请求数百毫秒,卡死可能持续几十分钟甚至永久不返回。
- 响应头到达,
http.Do()返回。 - SSE 流式返回事件(首包一到后续正常)。
WebSocket 模式(一段会话复用一条连接)
会话开始时只握手一次(TCP + TLS + HTTP Upgrade 为 WebSocket),连接升级为长期存活的双向管道。此后每一轮对话都只是往已有管道里发送轻量帧,应答也以帧返回。不再有 "新建 HTTP 请求 → 等响应头" 这一步骤。
卡死原因深入分析
以 CPA 为例,在 codex_executor.go:1121 处调用 httpClient.Do(httpReq),该调用发出请求并阻塞直到响应头到达。关键问题:这个 HTTP 客户端在等待响应头时没有任何超时设定。
helps.NewUtlsHTTPClient(ctx, cfg, auth, 0)构造时timeout=0,未设置client.Timeout- transport 层也未设
ResponseHeaderTimeout
因此 Do() 可在第④步无限期阻塞,上游卡多久就等多久。
日志跟踪发现两种可能形态:
- header-stall(首包/响应头卡死):连 200 响应头都迟迟不发,卡在第④步。逐帧日志显示选定上游后有 8.5 分钟空白才进入读流阶段,证明卡在
http.Do内部。 - body-stall(响应体卡死):200 头秒到,SSE 流开了却卡在首个内容事件。但健康请求中
response.created亚秒即到,无证据支持此为常见问题。
最终结论:开源在于 OpenAI 服务端收到请求后不干事,死活不发响应头。
解法方案(按优先级)
- OAuth 登录(几乎一定能解决):直接使用 OpenAI 官方 OAuth 认证绕过反代层问题。
- 配置允许 WebSocket:若无法 OAuth,在 CPA 或 sub2api 中配置允许 WebSocket 连接。在
config.toml的相应 provider 下添加supports_websockets = true(几乎一定能解决)。 - new-api + CPA:new-api 至今不支持 WebSocket,需自行合并 PR #5062,然后开启 CPA 配置允许 WebSocket,并在
config.toml中配置上述字段(几乎一定能解决)。 - 超时重连逻辑(不开 WebSocket 兜底):反代客户端需要实现「首包超时 → 同号重连」逻辑。具体方法:给第④步套一个预算
first-event-timeout-seconds(设为 6 秒),将ExecuteStream丢入 goroutine,超时则丢弃卡住请求,用同一账号重新发起(first-event-retries设为 4)。健康请求首字节 1~3 秒到,6 秒充足;卡死则零字节。实测多数一次或两次重试即可恢复。此为不得已之方案,需二开 CPA 或 sub2api。
WebSocket 为何能绕过首包卡死
WebSocket 的本质是只握手一次,将 HTTP 连接升级为持久双向管道。在 HTTP/SSE 中,每一轮对话都要经历一次 "等响应头"(第④步),而 WebSocket 仅在会话开始时有一次握手(返回 101 很快),之后每轮对话只是帧的收发,不再有等待响应头这一步。因此直接规避了概率卡死点。
关键要点
- 核心问题:OpenAI API 在 HTTP/SSE 模式下随机出现响应头延迟(首包卡死),导致 Codex 无限等待。
- 根本原因:CPA 等反代客户端的
http.Do()调用未设置任何超时,可无限期阻塞。 - 最优解法:使用 WebSocket 模式(通过 OAuth 登录或配置
supports_websockets = true),将等响应头的步骤压缩为仅一次握手,后续对话不再触发。 - 兜底解法:实现首包超时机制(6 秒超时 + 同账号重试 4 次),实测能绕过单次请求的坏运气。
- WebSocket 模式下,连接复用显著减少延迟和卡死概率;生产观测中每个会话只记一条连接日志,而 HTTP/SSE 每轮请求都产生独立日志。
- 无论 OAuth 还是 WebSocket 配置,几乎都能解决卡死问题;不开 WebSocket 则必须依赖超时重连机制,需要修改反代代码。
意义与影响
这次调查不仅解决了 Codex 用户的具体痛点,更揭示了 API 交互模式选择对 AI 工具稳定性的关键影响。研究清晰定位了 "首包卡死" 的根源在 OpenAI 服务端,并给出了从配置到代码层面的系统化解决方案。WebSocket 作为一种更高效的通信协议,在需要持续交互的 AI 编程助手中展现出明显优势——它消除了逐请求建立连接和等待响应头的开销与风险,提升了用户体验。同时,社区驱动的问题排查过程(日志追踪、时序图分析、代码定位)为类似问题提供了可复用的方法论。对于反代工具开发者来说,此案例也敲响了警钟:默认无超时的 HTTP 客户端配置在生产环境中极其危险,合理的超时和重试策略应当成为标配。未来,AI 工具链可能会更广泛地采用 WebSocket 或类似长连接协议,以规避此类阻塞问题。
