ChatGPT 桌面端状态点离线的排查与修复脚本
原创 · 约 23 分钟阅读 · 阅读 --

ChatGPT 桌面端状态点离线的排查与修复脚本

作者: Alex Xiang


本文完全由 WorkBuddy 独立收集资料、调试代码并完成文章写作。

扫码注册 WorkBuddy 即可获取 2000 积分

我是 Alex Xiang,前百度/微博工程师,现在专注于 AI 工程与工具产品。更多文章欢迎关注微信公众号「字与码」。

这个故障有两个看起来毫不相关的症状,但它们是同一件事。

第一个症状:桌面上那个启动脚本突然报错,「应用程序无法启动,因为应用程序的并行配置不正确」。第二个症状:能进应用,但界面里表示本机 Codex 在线状态的那颗小圆点一直是灰的,模型列表拉不出来。

先说结论:第一个是路径问题,第二个是网络栈问题,两者都不需要重装。修复后的启动脚本一共 7 行。

笔记本旁的琥珀色状态灯

症状一:并行配置不正确

这句报错来自 Windows 的加载器,字面意思是「程序清单里声明的依赖,找不到」。但在这个场景里,它和运行库无关。

看原来的脚本:

@echo off
set "HTTP_PROXY=http://127.0.0.1:1080"
set "HTTPS_PROXY=http://127.0.0.1:1080"
start "" "C:\Program Files\WindowsApps\OpenAI.Codex_26.1013.4894.0_x64__2p2nqsd0c76g0\app\ChatGPT.exe"
pause

那个路径里的 26.1013.4894.0 是包版本号。查一下实际安装的版本:

Get-AppxPackage OpenAI.Codex | Select-Object Version, InstallLocation

返回的是 26.1016.8622.0——应用在 10 月 23 日自动更新过,目录名变了,脚本还在指旧目录。

诡异的地方在于:旧目录并没有被删除。它还在,里面还有 ChatGPT.exe,甚至 Test-Path 返回 True。但内容已经不完整了:正常版本有 35 个文件,旧目录只剩 12 个。这种「残留空壳」比直接消失更麻烦——系统不会报「找不到文件」,而是加载器读到一个残缺的清单和缺失的依赖,于是抛出那句与实际情况完全对不上的「并行配置不正确」。

实测确认了这个判断。直接执行两个路径的 exe:

启动目标结果
OpenAI.Codex_26.1013.4894.0(旧)抛 SxS 异常
OpenAI.Codex_26.1016.8622.0(当前)正常启动,8 个进程存活

结论一:Store 应用的安装目录名里带版本号,任何硬编码路径都会在下一次更新后失效。

顺带一句:旧版本的残留目录不用手动删。C:\Program Files\WindowsApps 的 ACL 是受限的,手动改动容易破坏包注册,交给系统在后续清理时回收更安全。

症状二:状态点为什么是灰的

一个应用,两个进程

这里要拆开一个常见误解:ChatGPT 桌面端并不是「一个程序」,而是两个进程、两套网络栈。

包里的文件结构是这样的:ChatGPT.exe(4.8MB)配着 626MB 的 app.asar、chrome.dll、icudtl.dat——这是一层 Electron 外壳。旁边还有一个 resources\codex.exe,336MB,是 Rust 编译的 CLI。

应用启动时,外壳会把 CLI 作为子进程拉起来,两者通过标准输入输出跑 JSON-RPC。这一点在日志里能直接看到:

rpc.transport="stdio"
app_server.client_name="Codex Desktop"
client_version="26.1016.62342"
Request completed url=https://chatgpt.com/backend-api/codex/models status=200 OK
   ← codex_http_client::client

transport="stdio" 说明连接方式是本地管道,client_name="Codex Desktop" 说明外壳只是客户端。而所有真正出网的请求——模型列表、插件目录、在线状态——都记在 codex_http_client 这个 Rust 模块名下。

也就是说:界面上那颗表示「本机 Codex 在线」的圆点,判定依据是 CLI 拉取模型列表是否成功,跟外壳自己的联网状态无关。

代理配置不共享

两套栈的关键差异在代理上:

  • Chromium 那一套,走 Windows 的 WinINet,也就是「设置 → 网络 → 代理」里那个系统代理;
  • Rust 那一套,走 reqwest,只读进程环境变量 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY,完全不看 WinINet。

在 CLI 的二进制里能搜到这些变量名,大写出现 13 次,大写加小写合计二十多处,就是这段读环境的代码。反过来,「系统代理」相关的 WinINet API,Rust 侧一个都没有。

这不是 ChatGPT 独有的毛病。凡是「Electron 外壳 + 原生子进程」的应用,都可能踩同一颗雷:VS Code 的扩展宿主、Discord 的语音进程、各种 IDE 的调试器,出网走的是各自的 HTTP 客户端,代理配置并不共享。判断方法很统一——先确认真正发请求的是哪个进程,再看那个进程用的是哪套网络库。

一条程序分出两条通路,一条通畅一条堵塞

于是现象就解释通了:系统代理开着,外壳能上网,但 CLI 拿不到代理,请求超时,圆点变灰。

两条互不相干的网络路径

为什么必须「设定环境变量再启动」

环境变量是进程创建那一刻从父进程复制过来的,之后不会再变。

所以同样是「启动 ChatGPT」,两种启动方式的继承结果完全不同:

两种启动方式的环境继承差异

启动方式谁能拿到你设的代理变量
start "" "%EXE%" 直接运行 exe能。exe 是 cmd 的子进程,完整继承
start "" shell:AppsFolder\...!App 包身份激活不能。由 Explorer 代为拉起,继承的是 Explorer 的环境

这就是为什么「设环境变量再启动」这个思路本身是对的,而且只有直接启动 exe 这条路才成立。顺带说明:setx 写持久化环境变量也能生效,但那会污染全局,为了一个启动动作不值得。

验证:三条命令看清真相

CLI 自带一个诊断子命令,路径在 %LOCALAPPDATA%\OpenAI\Codex\bin\<hash>\codex.exe:

:: 不带代理环境变量
codex doctor

:: 带代理环境变量
set HTTPS_PROXY=http://127.0.0.1:1080 && codex doctor

对比两次输出的 Notes 段。不带变量时会出现 Responses WebSocket timed out,带上之后这条消失——说明变量确实被 CLI 读到了。

想看得更细,日志库在 ~\.codex\logs_2.sqlite,表名 logs,feedback_log_body 列是 JSON 字符串。Node 22 自带 node:sqlite,十几行就能查出每次请求的结果:

const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync('C:\\Users\\ax2\\.codex\\logs_2.sqlite', { readOnly: true });
const rows = db.prepare(`SELECT ts, level, feedback_log_body FROM logs
  WHERE feedback_log_body LIKE '%backend-api/codex/models%' ORDER BY id DESC LIMIT 20`).all();
for (const r of rows) {
  const b = r.feedback_log_body;
  console.log(new Date(r.ts * 1000).toLocaleString('zh-CN'),
    (b.match(/status=(\d+)/) || [])[1] || (/timed out/.test(b) ? 'TIMEOUT' : r.level));
}

如果输出里 200 和 TIMEOUT 交错出现,那就是链路抖动,不是配置错误——这时候该换节点,而不是继续改脚本。

修复后的脚本

@echo off
set "HTTP_PROXY=http://127.0.0.1:1080"
set "HTTPS_PROXY=%HTTP_PROXY%"
set "ALL_PROXY=%HTTP_PROXY%"
set "NO_PROXY=localhost,127.0.0.1,::1"
for /f "usebackq delims=" %%i in (`%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -Command "(Get-AppxPackage OpenAI.Codex).InstallLocation"`) do set "PKG=%%i"
start "" "%PKG%\app\ChatGPT.exe"

逐行说清:

  1. 三个代理变量指向本地代理。ALL_PROXY 给不认 HTTP 代理的客户端兜底。
  2. NO_PROXY 放行 localhost 和 127.0.0.1。这行不能省,否则本地环回也会被塞进代理,CLI 和外壳之间的本地连接可能被劫持。
  3. 第 5 行是关键:用 Get-AppxPackage 取安装位置,每次运行都重新解析,版本号怎么变都不影响。
  4. 最后直接启动 exe,让子进程继承上面四个变量。

如果应用已经在运行,新设的变量不会穿透到旧进程,得先结束再启动。一行搞定:

taskkill /f /im ChatGPT.exe >nul 2>&1 & timeout /t 2 >nul & start "" "%PKG%\app\ChatGPT.exe"

四个坑

坑一:for /f 里的引号会被吃掉。给 PowerShell 全路径加引号:

for /f "usebackq delims=" %%i in (`"%SystemRoot%\...\powershell.exe" -NoProfile ...`) do set "PKG=%%i"

执行时报 'C:\...powershell.exe" -NoProfile -Command "' 不是内部或外部命令,%PKG% 变成空值,最后报「系统找不到文件 \app\ChatGPT.exe」。这个路径本身不含空格,去掉引号即可。

坑二:不要同时设大写和小写代理变量。网上很多教程把 HTTP_PROXY 和 http_proxy 一起写上,那是 Linux/macOS 的习惯。Windows 的环境变量不区分大小写,两个键名只差大小写,会让 .NET 的 Process.Start 直接抛异常:

已添加项。字典中的关键字:"HTTP_PROXY"所添加的关键字:"http_proxy"

大写一套就够,reqwest 查询时也不区分大小写。

坑三:.bat 里不要写中文注释,文件必须是 CRLF。cmd 在 chcp 生效前按系统代码页解析文件,UTF-8 的中文字节会被拆碎,注释行被当成命令执行,屏幕上冒出一串 'e' is not recognized。要么全英文注释,要么存成 GBK——但 GBK 在别的工具里又容易乱码,最省事的是纯 ASCII 加 CRLF。

坑四:别用包身份激活来「修复」启动失败。用 shell:AppsFolder\OpenAI.Codex_2p2nqsd0c76g0!App 确实能稳定启动,看起来是更好的方案。但它由 Explorer 拉起,你设的代理变量传不进去,症状二会原样复发。

另一种思路:TUN 模式

如果代理客户端开启了 TUN(虚拟网卡),流量在网络层就被接管,进程根本不需要知道代理的存在,环境变量也就无所谓了。两种做法各有取舍:

环境变量TUN 模式
生效范围只有继承到变量的进程全系统,含不认代理的客户端
权限要求普通用户通常需要管理员
可能影响几乎无局域网、Docker、其他虚拟网卡
适用场景只想让某个应用走代理整机都要走代理

选择环境变量的理由很简单:只想影响 ChatGPT 这一个进程,不想动全局路由。代价是每次启动都得经过这个脚本,忘了点就回到原点。

边界

这套脚本解决的是「本地 CLI 拿不到代理」,不解决别的。

如果日志里一直是超时而不是 200,问题在网络链路。实测这台机器经代理访问 chatgpt.com,五次里四次拿到响应(403 属正常,无凭证访问被 Cloudflare 拦),一次超时;API 端点返回 401 也属正常。约 20% 的丢包率会让圆点反复闪烁——这种情况改脚本没有意义。

另外,企业代理需要认证、或者代理本身是 SOCKS5 时,要写成 socks5://127.0.0.1:1080,reqwest 支持,但 WinINet 那一侧的行为就不一定一致了,两边可能对不上。

最后一个提醒:codex doctor 里那条 ✗ desktop update and runtime CDN is unreachable 标着 optional,探测的是 persistent.oaistatic.com 和 chatgpt.com,跟状态点不是同一项。看到红色叉号不必紧张,先确认是哪一条在报错。

参考资料

  • codex doctor 的实际输出(本地 CLI v0.163.0-alpha.11.2)
  • ~\.codex\logs_2.sqlite 中 logs 表的请求记录
  • 包内目录结构与 AppxManifest.xml 的应用声明
打开原图 ↗