news 2026/10/10 3:21:35

Codex一直显示“正在思考”卡住?排查WebSocket握手失败与HTTPS回退方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Codex一直显示“正在思考”卡住?排查WebSocket握手失败与HTTPS回退方案

最近帮同事排查了一个问题,搞得我一度怀疑是 Codex 本身抽风:对话框里一直显示“正在思考”,转圈转个没完,可等上十几分钟也不见一个 token 出来。翻日志发现请求全部卡在/responses这个端点上,再往下追,问题出在 WebSocket 长连接一直握手失败。最后把传输方式从 WebSocket 回退到 HTTPS 短连接,前后不过几分钟,一切恢复正常。

这篇文章把这次完整的排障过程记录了下来。包括哪些日志值得看、为什么“正在思考”会骗人、怎么用手边的命令确认 WebSocket 被网络链路“暗杀”,以及如何配置 HTTPS 回退。如果你也在用 Codex 做 AI 编程,碰到过类似的无响应、卡顿、转圈不结束的问题,这篇文章估计能帮你省下好几个小时。

1. 先还原现场:Codex“正在思考”卡住时我看到了什么

1.1 现象:不是崩溃,而是无限等待

那天的现象很典型。我在某个项目的自动化代码生成流程里用 Codex,大约上午十点开始,开发机上所有对话都不正常:输入问题后,界面上的状态变成了“正在思考”,然后就没有然后了。

最折磨人的是它不报错。不是那种红色错误提示,也不是超时弹窗,就是安静地转圈。你点取消倒是能取消,但重新发送一遍,还是一样卡住。我试过把问题从一句话扩写成一段详细需求,没用;换成简单到不能再简单的“1+1等于几”,也没用。看起来跟输入内容无关,是通信链路的问题。

1.2 第一手资料:日志里藏着真正原因

一开始我按老套路来,先重启 Codex。这里要多说一句:重启确实能解决一些临时性的进程问题,但如果是网络链路的问题,重启一百遍也没用。

我翻到了 Codex 的本地日志目录,在~/.codex/logs/下,按日期滚动。用的命令很简单:

tail -f ~/.codex/logs/codex.log | grep -iE "websocket|wss|upgrade|responses"

日志里很快就刷出了关键信息:

[ERROR] websocket dial: wss://api.example.com/v1/responses: connection reset by peer [WARN ] transport fallback skipped: timeout waiting for 101 Switching Protocols [WARN ] retry after 5s, attempt 2/5 ...

看到这里,我基本锁定方向:WebSocket 握手失败了。第二行的101 Switching Protocols是 WebSocket 握手成功的标志,没有 101,就意味着连接没有升级成功。

1.3 影响范围:不是所有请求都失败

为了确认到底是 Codex 的问题还是后端模型服务的问题,我用普通 HTTPS 请求打了一下同一个服务的普通接口:

curl -sS --max-time 15 https://api.example.com/v1/models | head -20

结果返回正常,HTTP 200,数据完整。

这时候我得出一个初步结论:后端服务本身没有问题,出问题的是流式传输通道。于是我把现象做成了一张简单的对照表:

检查项结果
普通 HTTPS 请求正常返回,无超时
WebSocket 握手请求连接被重置,无 101 响应
Codex 本地转发进程健康检查进程存活,但转发失败
模型服务状态日志显示服务端已收到请求,但无法建立流式通道

这张表说明:链路的前半段通了,后半段断了。接下来要重点排查的就是 WebSocket 这条长连接链路。

2. 排障思路:从“卡住”到锁定 WebSocket 的每一步

2.1 为什么 Codex 偏爱 WebSocket?

先解释一个基础问题:为什么 Codex 不老老实实用普通 HTTPS,非要优先尝试 WebSocket?

因为 AI 编程助手的回复是流式的。你在界面里看到的一句句代码、一段段解释,其实是一个个 token 随着生成过程陆续推送到客户端的。WebSocket 是全双工长连接,服务端生成一个 token 就能推一个,客户端立刻展示,体验类似“打字机”效果。如果用普通 HTTPS 请求,客户端就得等服务端把整段内容生成完,一次性拿到完整结果,体验会差很多。

一句话概括:WebSocket 像打电话,两边随时可以说话;HTTPS 请求-响应更像寄快递,你发一个单子,对方把东西打包好再寄回来。

但 WebSocket 对网络链路的要求也更高。中间任何一个网络设备不支持协议升级,或者对长连接做了强制超时,就会出现“看着正常、实际不通”的情况。

2.2 本地代理在这条链路里的角色

Codex 在本地通常会启动一个转发进程,也就是日志里经常提到的 local proxy。这里先澄清一下,别把它理解成上网代理,它是 Codex 内部做请求转发的小进程:Codex 界面先把请求发到本机的某个端口,再由这个端口转发到模型服务。

完整链路是这样的:

Codex 界面 -> 本地转发进程 -> 公司出口网关 -> 模型服务

日志里出现这样一句话时,基本可以确定本地转发进程在处理/responses这个端点时出了问题:

ERROR switch local proxy failed while handling codex endpoint /responses. provider: ...

意思是:客户端已经生成了发送请求的任务,但本地转发进程切换目标时失败了。因为/responses是流式响应的核心端点,一旦这里卡住,界面上就会永远停在“正在思考”。

检查本地转发进程是否存活,我用的命令:

# 找到 Codex 相关进程监听的端口 lsof -iTCP -sTCP:LISTEN -P | grep -i codex # 对本机端口做健康检查,端口号以上一步输出为准 curl -s --max-time 5 http://127.0.0.1:17365/health

如果本地进程已经死了,或者端口被别的程序占用,转发肯定失败。但这次发现进程还活着,健康检查也返回了 OK,说明问题不在本地。

2.3 排查命令清单:一步步缩小范围

我把整个排查过程整理成了一个清单,方便照着做:

  1. 查日志:grep -iE "websocket|wss|upgrade" codex.log,确认有没有握手失败记录。
  2. 模拟握手:用 curl 发送 WebSocket Upgrade 请求,观察服务端是否返回 101。
  3. 查环境变量:env | grep -iE "proxy|codex|transport",确认没有配置导致流量绕路的错误参数。
  4. 查本地转发进程:用lsof和curl确认端口存活。
  5. 换传输方式:在配置里强制关闭 WebSocket,改用 HTTPS。

这五步做完,基本能把问题缩小到“WebSocket 链路被阻断”这个结论上。

3. 深入根因:为什么握手会失败,回退机制为什么没生效

3.1 WebSocket 握手的原理和脆弱点

WebSocket 的握手实际上是基于一次普通 HTTP 请求完成的。客户端发送一个带有特殊头部的 HTTP 请求,比如:

curl -i --max-time 10 \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Key: c2VjcmV0LWtleQ==" \ -H "Sec-WebSocket-Version: 13" \ https://api.example.com/v1/responses

如果服务端同意升级,会返回:

HTTP/1.1 101 Switching Protocols

看到101,才算握手成功。如果中间某个节点没把这个 Upgrade 请求放行,它可能直接吞掉后续数据包、返回 502,或者干脆一直不响应。客户端这边没有收到明确的失败信息,就会一直等。

这次的情况属于典型的“静默丢弃”。请求到达了公司出口网关,但网关对Upgrade: websocket这条请求没有放行,导致后续所有数据都进不来。客户端看着连接还挂着,实际已经是一个“假连接”了。

3.2 “正在思考”的假象是怎么来的

很多人遇到“正在思考”会以为是模型推理慢,其实不一定。Codex 界面上只要请求没有被终止,就会一直显示“正在思考”。问题在于,TCP 连接被中间设备静默切断时,客户端进程并不一定会立即感知。

WebSocket 协议本身有心跳机制,也就是 ping/pong 帧,用来探测连接是否存活。但如果握手都还没完成,客户端压根还没进入正常的 ping/pong 循环,它只能一直等那个永远不会来的101 Switching Protocols。

我用一个比喻解释给同事听:就像你打电话,拨出去之后对方一直没接,但听筒里也没有“暂时无法接通”的提示,你就只能一直举着电话干等。Codex 的界面就是在“举着电话干等”的状态。

3.3 复现与确认:用客户端开关验证根因

判断问题到底是不是 WebSocket 造成的,最直接的办法是强制关掉 WebSocket,让它改走普通 HTTPS。

在 Codex 的配置文件里,一般会有一个传输方式的配置项。我这边使用的字段是transport,修改之前先备份:

cp ~/.codex/config.toml ~/.codex/config.toml.bak

然后改成:

transport = "https"

重启 Codex。重启后发同样的问题,如果“正在思考”立刻消失,内容正常输出,根因就确认了。

这里提醒一句:每次只改一个变量。不要同时改网络配置和传输配置,也不要在改完配置的同时重启了出口网关,否则你根本不知道是哪一步起的作用。

4. 回退 HTTPS 的落地配置与效果验证

4.1 快速回退操作

如果你现在也遇到了同样的问题,最快的方式是设置环境变量。不同版本的 Codex 支持的变量名可能不太一样,可以用帮助命令确认:

codex --help | grep -i transport codex config list | grep -iE "transport|websocket"

我这边可用的环境变量是CODEX_TRANSPORT,设置方式:

export CODEX_TRANSPORT=https

如果想更明确地禁用 WebSocket,也可以尝试:

export CODEX_DISABLE_WEBSOCKET=1

但要注意,不是所有版本都支持这两个变量,不支持的版本会直接忽略,然后继续走 WebSocket。所以改完之后一定要看一眼日志,确认配置真的生效了。

配置文件的方式更持久。在~/.codex/config.toml里加上:

transport = "https"

保存后重启 Codex。重启后日志里如果能看到using https transport或者类似的字样,说明回退成功。

4.2 验证连接恢复正常

配置生效后,我做了几组简单对比:

  1. 清空日志文件,方便观察最新输出:
    > ~/.codex/logs/codex.log
  2. 在 Codex 里发一个中等复杂度的编码任务,例如“写一个 Python 脚本,批量重命名某个目录下的所有文件”。
  3. 观察首 token 响应时间。

结果差异非常明显:

模式首次 token 响应日志表现连续 20 次请求成功率
WebSocket(默认)无限等待大量websocket dial和101 timeout错误约 15%
HTTPS(回退)约 3~5 秒无 WebSocket 错误100%

我还顺手测了一个比较复杂的代码生成场景,HTTPS 模式下整体生成时间没有明显变长。原因很简单:模型生成 token 本身才是最耗时的部分,传输方式带来的额外握手开销基本可以忽略。

4.3 长期方案:让 WebSocket 也能稳定工作

回退 HTTPS 只是避开了问题,并没有消除问题。如果你希望以后还能用 WebSocket 获得更顺畅的流式体验,可以考虑下面几个方向:

  1. 调整网络出口设备,允许443端口上的Upgrade: websocket请求通过。公司网络如果由统一网关管理,这一步需要网络管理员配合。
  2. 配置NO_PROXY,让模型服务的请求绕过本地转发进程,直接走直连出口,减少一层转发干扰。
  3. 升级 Codex 到新版本,有些版本对“等待 101 超时后自动回退”的逻辑做得更好,不会傻等十分钟。
  4. 如果只是临时在办公网里用,保持 HTTPS 回退是最省心的方案。

注意:不要同时设置transport、use_websocket和CODEX_TRANSPORT多个配置,如果它们互相冲突,客户端可能启动的时候直接报配置错误。

5. 避坑清单与常见问题速查

5.1 排障速查表

我把这次排查以及之前几次类似问题归纳成了一张速查表,遇到相同症状可以直接查:

症状可能原因快速排查方法处理方式
一直“正在思考”,无错误提示WebSocket 握手失败,回退未触发日志搜websocket、101强制切换到 HTTPS 传输
日志出现local proxy failed本地转发进程端口冲突或状态异常lsof查端口,curl查健康接口重启 Codex,检查端口占用
偶发卡顿,重启后恢复WebSocket 长连接被网络设备空闲超时断开日志搜timeout、keepalive缩短心跳间隔,或改用 HTTPS
普通 HTTPS 请求正常,只有对话卡住中间设备不支持 Upgrade 头用 curl 模拟握手,观察是否返回 101关闭 WebSocket,强制走 HTTPS
环境变量改了没生效配置字段名写错,或当前版本不支持codex config list确认字段名按当前版本支持的变量名修改

5.2 我在这次排障里踩过的三个坑

第一个坑是过度依赖重启。前 20 分钟我一直在重启 Codex,状态确实偶尔能好一下,但下次请求又卡住。后来想明白了:重启只是把未完成的连接全部清掉,相当于强行挂断那通没人接的电话,但下次拨号还是走同样的失败路径。

第二个坑是只测了普通接口,没测 WebSocket 握手。刚开始我用 curl 请求了一次/v1/models,看到返回正常就以为后端没问题,差点把方向带偏。实际上普通的 HTTPS 短连接和 WebSocket 长连接在网络链路上完全是两套命运,必须针对性地验证101响应。

第三个坑是改完配置之后没看日志。我在配置文件里加了一行transport = "https",重启后发现还是卡住,一度以为这个方法无效。后来才发现是环境变量里残留了一个旧配置,优先级更高,直接覆盖了配置文件。所以改完配置后,一定要去看日志确认实际加载的参数是什么。

5.3 给同样被“正在思考”折磨的朋友一句总结

我个人在多次处理这类问题后的体会是:当你看到“正在思考”四个字,不要第一反应去怀疑模型变笨了,先确认链路还通着。尤其是 Codex 这类重度依赖流式传输的工具,WebSocket 握手成功与否,直接决定你能不能收到内容。学会看101 Switching Protocols这个状态码,比会敲多少花哨命令都实在。

如果你也碰到类似情况,优先按这个顺序处理:看日志,确认是 WebSocket 还是普通 HTTPS,再决定要不要回退。大部分时候,让 Codex 老老实实走 HTTPS 回退,是最不折腾人的方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 3:21:14

.NET企业门户网站完整版:三层架构、部署实战与性能优化

简介:企业网站开发常面临功能完整性与可维护性的双重挑战,分层架构将数据访问、业务逻辑与页面表现分离,降低模块间耦合,便于独立调整与后续扩展。在.NET技术栈中,这种设计结合参数化查询、输出缓存、IIS部署配置等技术…

作者头像 李华
网站建设 2026/10/10 3:21:11

从diff到patch:Linux补丁应用踩坑与实战解析

你刚从某个协作渠道拿到一个.patch文件&#xff0c;同事说“把这个补丁打上去&#xff0c;构建就能过了”。结果你敲下patch -p1 < xxx.patch&#xff0c;终端却甩给你一行 “Hunk #1 FAILED at 38” 的红色报错。这种场景在 Linux 开发里太常见了&#xff0c;尤其做内核移植…

作者头像 李华
网站建设 2026/10/10 3:20:43

PCA9422+STM32L041C6 低功耗电源管理系统设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 3:20:23

C++函数重载、隐藏与虚函数覆盖:从函数签名到vtable底层原理

1. 同名函数的三条岔路&#xff1a;重载、隐藏与覆盖先说一个我在刚接触C时花了很久才转过弯来的结论&#xff1a;函数重载、函数隐藏、虚函数覆盖这三件事&#xff0c;虽然都和“同名函数”有关&#xff0c;但它们的决策时机完全不同。重载发生在编译期的同一个作用域里&#…

作者头像 李华
网站建设 2026/10/10 3:20:01

docker-agent:用 YAML 声明式编排 AI 智能体工作流

1. 项目概述&#xff1a;这不是又一个 Docker 封装工具&#xff0c;而是一次 AI 工作流范式的迁移“Docker 开源 docker-agent&#xff1a;用 YAML 声明式构建和运行 AI 智能体”——这个标题里藏着三个被多数人忽略的信号&#xff1a;docker-agent 不是 Docker 插件&#xff0…

作者头像 李华