平台工程

代理环境变量遇 TUN 双重路由 · Codex CLI 流式超时排障

入档:2026-08-04 来源:一次「Codex CLI 执行指令时反复显示『正在重新连接 2/5』」的排障会话;本机 Windows 10 Pro + Mihomo Party(mihomo v1.x,TUN 模式 + 系统代理 127.0.0.1:7890 同时开启),Codex CLI 0.146.0-alpha.9.2 状态:已定位并修复,注册表 + 启动脚本 + vpn-guard 三处落地;验证 10 次取平均,WSS 重连端点延迟下降 85%

事实记录(不可修改区)

症状与首轮诊断

关键发现:Codex 的 API 端点不是 api.openai.com

日志里所有请求都打向 chatgpt.com/backend-api/...,而非 api.openai.com。首轮测试只测了 api.openai.com,延迟正常(0.85s),差点误判”网络没问题”。切换到 chatgpt.com 端点后才暴露问题。

端点HTTPTLS 握手首字节
api.openai.com/v1/models4010.85s1.24s
chatgpt.com/4031.09s1.39s
chatgpt.com/backend-api/codex/models4012.54s3.83s
chatgpt.com/backend-api/wham/usage4011.55s2.01s
WSS chatgpt.com/wham/remote/control/server4003.47s4.57s

WSS 端点 4.57 秒——这正是日志中反复重连的那个连接。

根因:TUN + HTTP_PROXY 双重路由

注册表 HKCU\Environment 中存有用户级环境变量:

HTTP_PROXY=http://127.0.0.1:7890
HTTPS_PROXY=http://127.0.0.1:7890
ALL_PROXY=http://127.0.0.1:7890

Clash Party 配置同时开启了系统代理(sysProxy.enable: true)和 TUN 模式。于是 Codex CLI 的请求走了双重路由

Codex → HTTP CONNECT 隧道(127.0.0.1:7890) → mihomo 代理解析 → TUN 虚拟网卡 → VPN 隧道 → 目标

而 TUN 直连只需:

Codex → TUN 虚拟网卡 → VPN 隧道 → 目标

多出的 HTTP CONNECT 隧道层增加了 0.4–2.3 秒延迟。对短请求尚可忍受,但 SSE 长连接和 WebSocket 连接对延迟极敏感——握手阶段多出 2–4 秒,会触发客户端的超时重连逻辑,表现为”正在重新连接 2/5”。

对照实测(5 次 × 2 组)

chatgpt.com/backend-api/codex/models 首字节平均
HTTP_PROXY(代理路径)1.660s
HTTP_PROXY(TUN 直连)1.231s
差值0.429s

WSS 端点差距更大:

HTTP_PROXYHTTP_PROXY
WSS 总耗时4.57s0.70s
改善85%

修复

  1. HKCU\Environment 移除 HTTP_PROXYHTTPS_PROXYALL_PROXY(TUN 已在内核层路由,代理环境变量冗余)。
  2. 更新 NO_PROXY 加入 chatgpt.com,api.openai.com,*.openai.com(安全网)。
  3. vpn-guard 脚本(app-vpn.ps1)增加 TUN 活跃分支:显式将代理环境变量置空,避免子进程继承残留值。
  4. Codex CLI 启动脚本(launch-codex-cli.ps1codex.cmd)和 ChatGPT 桌面应用启动脚本(launch-codex.ps1)启动前清除代理环境变量。

修复后验证(10 次取平均)

端点修复前修复后改善
WSS 重连端点4.57s0.70s85% ↓
codex/models API2.30s1.47s36% ↓
wham/usage API2.01s1.01s50% ↓
api.openai.com1.73s0.77s55% ↓

一句话总结

TUN 已在内核层接管全部流量时,HTTP_PROXY 环境变量不会让请求”更安全”——它只会让请求多走一层 HTTP CONNECT 隧道,对 SSE / WebSocket 长连接来说,多出的 2–4 秒就是”正在重新连接”和”正常工作”的区别。

四条可复用 insight

1. TUN + 代理环境变量 = 双重路由,有 TUN 就不该设代理变量 ⚠️首次

TUN 模式在内核层创建虚拟网卡,接管所有出站流量(TCP / UDP / ICMP)。应用不需要知道代理的存在——它的数据包到了 TUN 网卡,mihomo 自然会路由。

但如果同时设了 HTTP_PROXY / HTTPS_PROXY,应用会走 HTTP CONNECT 隧道连到代理端口(127.0.0.1:7890),由 mihomo 代理解析后送入 TUN。这比 TUN 直连多了:

实测代价:普通 API 请求多 0.4 秒,WSS 握手多 3.9 秒。

判据:TUN 活跃时,代理环境变量是冗余的负担而非安全保障。 只有在 TUN 关闭、仅靠系统代理时,代理环境变量才对 CLI 工具有意义(因为 Node / Rust / Go 写的 CLI 不读 Windows 注册表里的系统代理)。

ping通不等于路通_fake-ip假信号与节点带宽实测选型_v1 互补:那篇讲”代理接管了 DNS 导致 ping 假绿”,本篇讲”代理接管了路由导致请求多走一跳”。共同点是——多出来的代理层不总是好事,它有自己的代价。

2. SSE / WebSocket 长连接对握手延迟远比短请求敏感 ⚠️首次

一次 API GET 请求多 0.4 秒,用户感知不到。但 SSE 流式连接和 WebSocket 连接的建立阶段如果多 2–4 秒:

本次 WSS 端点 4.57 秒 → 0.70 秒的改善,不是”快了一点”,而是从”必然超时重连”到”稳定不重连”的质变

判据:诊断流式连接问题时,不能只看”能不能连通”(HTTP code),必须测”握手要多久”(time_appconnect)和”首字节要多久”(time_starttransfer)。 一个 200 的响应如果用了 5 秒才到,对 SSE 来说就是坏的。

3. 测错了端点就看不到问题 ⭐ 首次

Codex CLI 的 API 端点是 chatgpt.com/backend-api/codex/...,不是 api.openai.com。首轮测试只测了 api.openai.com,延迟 0.85 秒,完全正常——差点收工。

是日志数据库里的 codex_http_client::client 模块记录了真实请求 URL(url=https://chatgpt.com/backend-api/codex/models?client_version=0.14...),才发现测错了目标。

判据:诊断”应用网络慢”时,必须从应用日志里确认它实际请求的 URL,不能凭文档或直觉假设。 同一个服务(OpenAI)的不同域名(api.openai.com vs chatgpt.com)可能走不同的 CDN 路径、不同的 TLS 配置,延迟可以差好几倍。

这与 OpenAI兼容止于对话端点_多提供商视频接口分流与真key首测_v1 的”模型名不许猜要拉列表”同族:别假设你知道应用在调哪个端点,去日志里看。

4. 用户级环境变量是全局的——vpn-guard 注入只影响它启动的那个进程,注册表里的影响所有进程 ⚠️首次

vpn-guard 脚本(app-vpn.ps1)的设计是”只作用于被启动的那一个进程”——通过 $inject 字典在子进程启动前设置环境变量,退出后还原。这没问题。

HKCU\Environment 里的 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY全局的——所有从资源管理器、终端、桌面快捷方式启动的进程都会继承。vpn-guard 不启动的进程(比如用户直接在终端里敲 codex)也受影响。

判据:进程级注入和系统级设置是两回事。 vpn-guard 的进程级注入是”给需要代理的程序额外加一层”,系统级环境变量是”给所有程序默认加一层”。当 TUN 已全局接管时,后者是多余的,且 vpn-guard 的进程级还原逻辑管不到它。

顺手教训

下次改进

复发记录(2026-09-08):同症状,不同根因

症状:Codex CLI(v0.153.4)再次反复「正在重新连接 4/5」,与本文主案一致。

诊断顺序与实测

  1. 先复查旧根因是否回滚:HKCU\Environment 仍无 HTTP_PROXY/HTTPS_PROXY/ALL_PROXY(本文修复保持完好),TUN 在线(Meta Tunnel,198.18.0.1)——双重路由未复发
  2. 端点小包测试:WSS 端点 TLS 握手 1.44s、首字节 1.9s,401/426 正常返回——偏慢但能通,容易误判为”没大问题”
  3. 吞吐实测现形:speed.cloudflare.com11.4 KB/s(≈0.09 Mbps),TLS 握手 8.99s——命中对外手册第五节「节点带宽垫底」。

修复与验证:Clash Verge GUI 手动切节点后复测:吞吐 11.4 KB/s → 8.49 MB/s(≈68 Mbps,745 倍);WSS 首字节 1.9s → 0.63-0.77s;models 端点 → 0.69-0.88s,全部回到或优于本文修复后基准。

环境变化记录:代理客户端已从 Mihomo Party 迁移至 Clash Verge(D:\Clash Verge\,verge-mihomo,混合端口 7897);external-controller API(9090/9097)依然不可用,切节点只能 GUI 手点(脚本切换不落盘 + 全节点扫描造成账号跨国跳变,均不可用)。

三条增量判据

复发记录·续(2026-09-08 下午):二阶根因——26MB 大会话 × WS 传输

换节点后用户反馈「修复失败,还在转圈」。深挖后锁定第二阶根因,比节点问题更本质:

错误本体与故障画像

排除过程(每项都有实测)

  1. WS 帧存活探针(Python 纯 socket 复刻 Codex 握手+发帧,绕过 IDE 沙箱代理注入):4KB 未压缩帧、协商 permessage-deflate 后的压缩帧,三轮全部收发正常——小帧过、大流死
  2. 代理变量路径分歧:HKCU/HKLM 注册表均无 PROXY 变量;Codex(Rust reqwest)与探针(Python socket)同走 TUN 直连——路径一致,排除。
  3. 自动回退验证:重试 5/5 耗尽后触发 falling back to HTTP(codex_core::client WARN),随后 POST /backend-api/codex/responses 200 OK,turn 正常推进——HTTP 通道能传 19MB,WS 通道不能

机制结论

修复(按优先级)

  1. /compact 或开新会话——请求体降回 0.1MB 级,WS 传输恢复秒级响应(轻量 turn 已实证正常)。
  2. 长会话必须继续时:容忍每 turn 的「15 秒重试 + 分钟级回退传输」,别在重试中途 Esc(中断会丢掉自动回退的机会)。
  3. 会话上下文逼近 auto_compact_scope_limit(本例 244800)前主动 compact。

四条增量判据

关联文档

类型/平台工程主题/网络排障主题/代理链路主题/Codex CLI