Windows 已开启代理,Codex 为什么仍然回复慢?一次 WebSocket 超时排障记录

排查日期:2026 年 10 月 15—16 日,时间均为北京时间(UTC+8)。

本文记录 Windows 上使用 v2rayN 后,Codex 桌面端仍长时间等待回复的一次排障。处理办法在本次环境中验证有效,不代表所有版本、所有慢请求都由同一个问题导致。

本次问题最明显的特征是:浏览器可以正常使用 ChatGPT,桌面端却经常迟迟没有输出。检查日志后,发现其中一次请求在 WebSocket 上反复超时,重试 5 次才切换到 HTTP;随后很快开始回复。

最终采用的办法是:通过一个专用快捷方式启动应用,只为该应用及其子进程设置 HTTP_PROXY 和 HTTPS_PROXY。重新启动后,WebSocket 正常复用,没有再出现本轮连接重试和 HTTP 回退。

1. 问题是怎么被发现的

最初的判断来自使用体验:本地桌面端有时等待数分钟,而网页端体感更快。

b0afe56e7b58408998a65550093ca4be.png

图 1:另一段历史桌面端对话的截图,用于说明“等待很久”的现象。界面标注为 9 分 54 秒,下方聊天分析又使用了 6 分 45 秒的口径,两者不一致;本文不把这张图当作精确计时或 WebSocket 超时的原始证据。

b914d69735074f729fbe201763bd31ab.png

图 2:网页端另一段对话显示“思考了 54 秒”。它与图 1 的问题、任务和计时口径不同,只说明使用体验上的差异,不能据此计算桌面端比网页端慢了多少倍。

排查时需要先分清三种时间:

指标 含义 本文如何使用
界面的“总用时” 可能包含思考、查资料、执行工具和生成回复 作为现象记录,不直接归因
界面的“思考了多久” 产品界面展示的思考耗时 不与另一任务的总用时直接比较
请求开始到首条助手消息的记录间隔 从会话中的 task_started 到首条助手消息记录 用于比较本次排查前后的首次回复等待

最后一项也不是严格的网络首字节时间(TTFB),更不是完整任务耗时。本文的 103.425 秒和 7.310 秒都采用这个会话记录口径。

2. 当时的运行环境

项目 本次实际检查结果
系统 Windows 桌面环境
应用 Windows 商店安装的 OpenAI.Codex,包版本 26.1007.9446.0
主程序 安装包内的 app/ChatGPT.exe
模型设置 gpt-6.1-sol,推理强度 high
代理软件 v2rayN 7.16.6,运行 Xray 核心
本地代理入口 127.0.0.1:20171,核心配置中的协议为 mixed
Windows 系统代理 已开启,地址为 127.0.0.1:20171
TUN 关闭
用户级、系统级代理环境变量 排查时均未设置 HTTP_PROXY、HTTPS_PROXY

8a528d4f00fe4b36a0efad7772ad4030.png

图 3:当时的托盘环境。托盘图标只能说明相关应用存在,不能单独证明代理连接正常。表中的端口、核心类型和 TUN 状态来自对本机进程、监听端口及配置文件的只读检查。

这里容易产生一个误解:“系统代理已经开启”并不等于“所有程序的每一种连接都已通过代理”。

v2rayN 的系统代理功能是把遵循系统代理设置的软件流量引导到核心入口;系统代理和核心内部的路由分流是两个环节。参见 v2rayN 官方 Wiki:系统代理和路由。

3. 关键证据:WebSocket 超时后才回退 HTTP

为避免仅凭聊天截图下结论,排查时读取了 Codex 的本地日志数据库和会话记录。

下面是 2026 年 10 月 15 日其中一次请求的脱敏时间线:

时间 事件 数据来源
23:40:53.556 任务开始,task_started 会话记录
23:40:56 左右 本轮上下文准备完成 会话记录
23:41:12 第 1 次超时重试 连接日志
23:41:27 第 2 次超时重试 连接日志
23:41:42 第 3 次超时重试 连接日志
23:41:58 第 4 次超时重试 连接日志
23:42:15 第 5 次超时重试 连接日志
23:42:33 回退 HTTP,falling back to HTTP 连接日志
23:42:36.981 首条助手回复消息记录 会话记录

从任务开始到首条助手消息,间隔为 103.425 秒。从回退 HTTP 到首条助手消息,只有约 4 秒。

连接日志中的关键片段如下。为方便阅读,已移除线程标识、目录、跟踪信息等无关字段;时间转换为北京时间,保留原始事件措辞:

23:41:12  stream disconnected - retrying sampling request (1/5 in 217ms)...
23:41:27  stream disconnected - retrying sampling request (2/5 in 429ms)...
23:41:42  stream disconnected - retrying sampling request (3/5 in 875ms)...
23:41:58  stream disconnected - retrying sampling request (4/5 in 1.584s)...
23:42:15  stream disconnected - retrying sampling request (5/5 in 2.9s)...
23:42:33  falling back to HTTP

原始重试记录还包含 sampling_error=request timed out。括号中的 217ms、429ms 等是下一次重试前的退避等待,不是每次连接尝试的完整耗时。

这组记录能直接支持的结论是:本轮首次回复前的大段等待与连接超时重试有关,不能全部归因于模型思考或查资料。它不能反向证明图 1 中另一轮数分钟的等待也一定出于相同原因。

4. 为什么浏览器正常,桌面端却会慢

4.1 Codex 的两种通信方式

本次日志显示,Codex 先尝试 WebSocket,失败后回退到 HTTP。这里切换的是通信方式,不代表换了模型。

方式 通信特点
HTTP 客户端发起请求,服务端返回响应;响应可以流式传输,文字逐步出现
WebSocket 建立持续连接,在同一连接上双向交换消息,适合多轮模型与工具交互

“HTTP 客户端”指应用内部负责 HTTP 通信的网络组件,不是另外安装的软件。WebSocket 的持续连接可以降低多次交互的开销,参见 OpenAI 官方说明:WebSocket Mode。该说明用于解释机制,不能代替对具体桌面版本的验证。

4.2 需要区分事实与原因推断

本机已验证的事实:

  • Windows 系统代理中已经存在 127.0.0.1:20171。
  • 这一次 WebSocket 超时重试 5 次,随后回退 HTTP 并成功输出。
  • 显式设置代理变量、重新启动应用后,WebSocket 正常复用,本轮不再出现重试和 HTTP 回退。

最符合这些事实的解释:

浏览器、Codex 的 HTTP 网络组件和 WebSocket 网络组件可能使用不同的代理解析逻辑。本次 WebSocket 路径很可能没有正确采用 Windows 系统代理,而显式环境变量让它获得了可用的代理配置。

Codex 项目中的 Issue #29958 也记录了“Windows 系统代理下 WebSocket 超时,显式设置 HTTP_PROXY、HTTPS_PROXY 后正常”的现象。这是同类用户报告,不是本机故障的源码级证明,也不表示所有版本必然存在该问题。

本次尚未直接证明的部分:

  • 失败的 WebSocket 请求是否实际尝试了直连。
  • 回退后的 HTTP 组件是否读取了 Windows 注册表中的系统代理。
  • 哪一段具体版本的源码导致了代理处理差异。

我们没有抓包,也没有调试对应版本的源码。因此,不应把“HTTP 组件读取系统代理,WebSocket 组件没有读取”写成已经观察到的原始日志。

浏览器访问正常,只能证明浏览器对应的访问路径可用;不能证明桌面应用使用了相同的接口、连接方式或代理路径。

5. 本地代理端口与服务端端口有什么区别

20171 不是 HTTP 的默认端口,也不是 Codex 的固定端口,它来自本机 v2rayN 的配置。

地址或端口 含义
127.0.0.1:20171 本机 Xray 的代理入口
https://chatgpt.com:443 HTTPS 服务端入口的表示方式
wss://chatgpt.com:443 安全 WebSocket 服务端入口的表示方式

HTTPS 和 WSS 在未写端口时通常使用 443。二者都可以先经过本地 20171 代理入口,再连接服务端。

本次 20171 是 HTTP/SOCKS 混合入口:核心可以识别不同代理协议。没有发现需要将端口拆开的证据,也没有为了处理本问题去拆分端口。

下面的配置含义是“访问 HTTPS 目标时,使用本地 HTTP 代理入口”:

HTTPS_PROXY=http://127.0.0.1:20171

变量名中的 HTTPS 描述目标连接类型,值中的 http:// 描述连接代理的协议,因此并不矛盾。这里不要凭目标使用 HTTPS,就直接把本地代理地址改成 https://。

另外,把 http://127.0.0.1:20171/ 当作普通网页打开,不能验证代理是否能正确转发目标连接;它首先是代理入口,不是一个必须提供首页的网站。

6. 解决办法:创建应用专用的代理启动入口

6.1 为什么选择应用级设置

把 HTTP_PROXY、HTTPS_PROXY 永久写成 Windows 用户环境变量比较方便,但其他读取这些变量的程序也可能受到影响。例如 Python 网络请求和依赖下载可能开始依赖本地代理是否运行。

本机有数据采集及开发工具,因此这次选择只在启动应用时设置变量。其他独立启动的软件不会因此继承它们;应用启动的工具和子进程则仍可能继承,不能理解为只影响应用界面本身。

6.2 操作步骤

以下步骤适用于本次检查到的商店安装包 OpenAI.Codex,其主程序为 app/ChatGPT.exe。CLI、独立安装包或不同包结构应先确认实际启动路径,不要直接套用。

  1. 在桌面空白处右键,选择 新建 → 快捷方式。
  2. 在“对象的位置”粘贴下面完整的一行。20171 要替换为自己实际的 HTTP 或混合代理端口。
powershell.exe -NoProfile -WindowStyle Hidden -Command "$env:HTTP_PROXY='http://127.0.0.1:20171'; $env:HTTPS_PROXY='http://127.0.0.1:20171'; $p=Get-AppxPackage -Name OpenAI.Codex; Start-Process -FilePath (Join-Path $p.InstallLocation 'app\ChatGPT.exe')"
  1. 点击“下一步”,命名为 ChatGPT(代理),然后完成。
  2. 保存工作,通过应用菜单或托盘菜单彻底退出 ChatGPT/Codex。只关闭窗口可能仍保留后台实例,已有进程不会因为再次点击快捷方式就自动获得新的环境变量。
  3. 确认 v2rayN 已运行、代理端口正确,再点击新建的快捷方式。
  4. 后续使用这个新入口。原来的开始菜单或任务栏入口不会自动附带上述变量。

85a3f4d8041649609d9b6c072ba67895.png

图 4:创建后的桌面入口。该截图展示图标,具体参数以快捷方式属性和上面的命令为准。

这条命令先在临时 PowerShell 进程中设置两个变量,再启动应用;它没有使用 setx,也没有写用户或系统环境变量。Get-AppxPackage 会查询当前安装位置,避免把带版本号的安装目录写死,但仍依赖安装包名称和内部程序路径保持兼容。

如果没有启动成功,可先用 Windows PowerShell 做下面的只读检查,确认包和主程序存在:

$p = Get-AppxPackage -Name OpenAI.Codex
$p | Select-Object Name, Version, InstallLocation
if ($p) {
    Test-Path -LiteralPath (Join-Path $p.InstallLocation 'app\ChatGPT.exe')
}

不要通过反复启动应用来排查路径问题;先确认现有实例状态和命令是否适用于自己的安装方式。

7. 如何确认这次修改生效

重新启动后,不能只凭“感觉快了”下结论,还需要核对下面几项:

检查项 本次验证结果
应用启动的工具进程是否收到变量 HTTP_PROXY、HTTPS_PROXY 均为 http://127.0.0.1:20171
WebSocket 是否正常使用 本轮日志出现连接复用记录
是否再次超时重试 核查窗口内,本聊天没有 responses_retry 记录
是否回退 HTTP 核查窗口内,本聊天没有 falling back to HTTP
模型列表是否仍刷新超时 本次重开后的核查窗口内没有发现
是否修改了 Windows 全用户设置 用户级、系统级两个代理变量仍为空

本次复核以 2026 年 10 月 16 日 00:37:42 为重开后观察窗口的起点,统计结果记录于约 00:39:40。其间当前聊天有 5 条 WebSocket 连接复用记录;这些是多次请求的记录,不代表建立了 5 条独立连接。

对应的首次回复时间如下:

对照请求 任务开始 首条助手消息记录 间隔
调整前,10 月 15 日 23:40:53.556 23:42:36.981 103.425 秒
调整后,10 月 16 日 00:37:51.882 00:37:59.192 7.310 秒

两次不是相同任务的严格基准测试,也没有控制服务器负载、上下文等全部变量。因此,不能把数值差直接当成普遍提速比例。更有说服力的是:显式代理变量生效的同时,本轮不再出现原先的连接超时重试和 HTTP 回退。

9412e468241f477d96c2f7cacae1bd0f.png

图 5:重开后检查结果的会话摘要。界面显示整个检查任务用时 1 分 53 秒,正文中的约 7.3 秒指首次回复等待;两者不冲突。截图本身是结果摘要,WebSocket 状态和计时结论来自另外读取的本机日志及会话记录。

8. 仍然慢时,接下来检查什么

按下面的顺序继续判断,不必一开始就更换模型或开启 TUN:

  1. 确认实际入口。 是否从新的代理快捷方式启动?应用是否彻底退出过?是否仍在复用先前的后台实例?
  2. 确认代理可用。 v2rayN 是否运行?端口是否变更?是否出现节点、TLS 或连接稳定性问题?
  3. 重新看日志。 若仍有 WebSocket 超时和 HTTP 回退,继续检查代理路径;若已没有这些事件,则检查模型处理和工具执行耗时。
  4. 区分首次回复与整个任务。 查网页、读取文件、执行命令都可能拉长总用时;high 推理强度和较长上下文也可能增加处理时间。
  5. 必要时做同条件对照。 保持模型、推理强度和问题一致,分别记录首次回复间隔及连接错误,避免只比较不同截图上的总时间。

本次匿名连接探测也曾出现 TLS 失败,说明不能宣称代理链路永远稳定;而匿名请求快速得到 403,只能证明某次访问获得了 HTTP 响应,不能证明带登录状态的完整 WebSocket 流程一定可用。

如何恢复原来的启动行为

先通过应用菜单正常退出,再从原来的启动入口打开即可。专用快捷方式不写全用户代理变量,退出相关进程后,不会留下这两个持久设置。恢复后是否再次出现延迟,需要另行观察;本次没有为了做反向试验而再次中断用户应用。

附录:可复查的位置与命令

A. Windows 系统代理

以下命令只读取设置,不修改注册表:

Get-ItemProperty -LiteralPath 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' |
    Select-Object ProxyEnable, ProxyServer, AutoConfigURL

本次读到 ProxyEnable = 1、ProxyServer = 127.0.0.1:20171。这证明系统代理配置存在,不证明每个应用都采用了它。

B. 本地代理是否在监听

Get-NetTCPConnection -State Listen -LocalPort 20171 |
    Select-Object LocalAddress, LocalPort, OwningProcess

监听存在只证明该端口上有程序接收连接,不代表节点和所有外部目标都可达。本次进一步核对了进程归属,确认为 Xray。

C. 当前进程与持久环境变量

# 只查看当前 PowerShell 进程收到的值。
$env:HTTP_PROXY
$env:HTTPS_PROXY

# 只查看用户级、系统级是否设置,不修改环境。
foreach ($scope in @('User', 'Machine')) {
    foreach ($name in @('HTTP_PROXY', 'HTTPS_PROXY')) {
        [pscustomobject]@{
            Scope = $scope
            Name = $name
            Configured = [bool][Environment]::GetEnvironmentVariable($name, $scope)
        }
    }
}

普通独立终端中的 $env: 只能说明那个终端的环境,不能用来判断另一个应用是否收到变量。本次是在应用启动的工具进程中核对值,再结合应用连接日志验证。

D. 本次读取的 Codex 本地记录

本次环境的状态目录为 %USERPROFILE%\.codex,其中:

  • logs_2.sqlite:连接日志数据库;排查时以 SQLite 只读模式查询,不修改或清理应用数据库。
  • sessions\2026\10\15\rollout-*.jsonl:本聊天的会话记录。本聊天创建于 10 月 15 日,跨日后仍写入原会话文件,不应只查 10 月 16 日目录。
  • 搜索关键词:responses_retry、request timed out、falling back to HTTP、responses_websocket、auth_connection_reused。

这些路径、文件名及字段属于本次版本的实现,不是跨版本稳定接口。读取时应限定聊天和时间范围,并区分重开前的历史错误;不要把数据库当普通文本日志全文打印,也不要公开登录令牌、认证文件或完整请求内容。

参考资料

本文使用的 5 张图片均为本次整理提供的截图,保留原文件名,通过相对路径引用。一起移动 codex-proxy-troubleshooting.md 和 images/codex-proxy/,即可保留文图关系。