这是本文档旧的修订版!
Codex 桌面版单应用代理配置(只让 Codex 走代理)
国内网络下,Codex / ChatGPT 桌面版登录经常会报这一句:
Token exchange failed: token endpoint returned status 403 Forbidden: Country, region, or territory not supported
奇怪的是,浏览器能正常打开 chatgpt.com,代理客户端也开着,偏偏 Codex 登录就 403。
原因是:Codex 的核心 app-server 是 Rust 写的,它只认环境变量 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY,不读 Windows 系统代理。用开始菜单磁贴(AUMID 激活)启动时,它继承的是用户级环境变量,而我们大多数人从没设过 → 登录请求直连出去,被地区拦。
本文的做法:不碰系统代理、不写用户环境变量、不开 TUN,起一个独立的 mihomo 实例,再用一个启动器把代理只注入 Codex 这一个进程树。影响面只有一个程序,删掉快捷方式就回滚。
(以下配置均为脱敏示例,节点栏位请替换成自己的。)
一、先确认是不是这个坑
| 现象 | 说明 |
| — | — |
| 浏览器能开 chatgpt.com | 代理本身没问题,问题在“程序没走代理” |
| Codex 登录报 403,且带 request id | 请求直连出去了,被地区拦 |
| 报错含 Country, region, or territory not supported | 基本可以确诊 |
| 换了节点、重装 Codex 都没用 | 因为流量压根没进代理 |
判读口诀:用 curl 打一下 token 端点看状态码。
400/401:链路是通的,只是参数或凭据不对,属于正常403:直连出去了,地区被拦000:根本没连上代理(节点挂了或端口不对)
二、四种方案对比
| 方案 | 影响范围 | 缺点 |
| — | — | — |
setx HTTPS_PROXY 用户环境变量 | 所有程序 | 污染 curl / git / npm / 各种 SDK,且要重新登录 |
| Windows 系统代理 | 全局 | 全局生效,且 Codex 的 app-server 根本不读它 |
| TUN 模式 + 进程名规则 | 按进程 | 需要管理员,规则常被机场订阅覆盖,排错难 |
| 独立实例 + 启动器(本文) | 一个进程树 | 需要自己维护一个 config.yaml |
三、原理:为什么"直接运行 exe"就行
实测结论(Windows 11 + MSIX 安装的 Codex):
- 用
shell:AppsFolder\OpenAI.Codex_xxx!App这种 AUMID 方式激活:不继承调用者的环境变量,读的是用户环境,所以注入无效 - 直接运行包内的
ChatGPT.exe:继承调用者的环境变量,codex.exe作为子进程能读到
所以启动器必须做三件事:
- 起自己的 mihomo 实例(独占一个端口)
- 给当前 PowerShell 进程设
HTTP_PROXY/HTTPS_PROXY/ALL_PROXY - 直接
Start-Process那个ChatGPT.exe,并补一个--proxy-server=(Chromium 那半边认这个参数)
四、端口规划
同一台机器可能同时跑好几个 mihomo,端口和 DNS 端口都不能撞。
| 实例 | mixed-port | DNS | 控制口 |
| — | — | — | — |
| 其他全局客户端 | 7890 | — | 9090 |
| Codex(本文) | 7898 | 1055 | 59098 |
| Claude 桌面版 | 7899 | 1054 | 59099 |
探测端口是否被占(Get-NetTCPConnection 在部分机器上要十几秒,别用它):
foreach ($p in 7890,7898,7899,1054,1055) { $c = New-Object System.Net.Sockets.TcpClient $iar = $c.BeginConnect('127.0.0.1', $p, $null, $null) $ok = $iar.AsyncWaitHandle.WaitOne(300) -and $c.Connected $c.Close() "{0,-6} {1}" -f $p, $(if ($ok) { '占用' } else { '空闲' }) }
五、配置文件
目录结构(放在用户目录下):
~/.codex/codex-proxy/ ├─ mihomo-codex.exe # 内核,故意改名,见下方说明 ├─ config.yaml # 端口 / 节点 / 规则 ├─ launch-codex.ps1 # 启动器 ├─ launch-codex.vbs # 静默包装,快捷方式指向它 └─ launcher.log # 启动日志
内核必须改名:很多客户端(含 Claude 桌面版的守护脚本)会用 Get-Process mihomo 按进程名清理,改叫 mihomo-codex.exe 就不会被误杀。
5.1 最小可用配置
mixed-port: 7898 allow-lan: false mode: rule log-level: warning external-controller: 127.0.0.1:59098 tcp-concurrent: true find-process-mode: always profile: store-selected: true store-fake-ip: false dns: enable: true listen: 0.0.0.0:1055 enhanced-mode: fake-ip fake-ip-range: 198.20.0.1/16 default-nameserver: [223.5.5.5, 119.29.29.29] nameserver: [223.5.5.5, 119.29.29.29] proxy-server-nameserver: [223.5.5.5, 119.29.29.29] proxies: - name: node-vless type: vless server: 127.0.0.1 # 换成自己的服务器 port: 443 uuid: 00000000-0000-0000-0000-000000000000 tls: true servername: example.com # 伪装域名(SNI) client-fingerprint: chrome flow: xtls-rprx-vision reality-opts: public-key: KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio short-id: 01234567 skip-cert-verify: true udp: true proxy-groups: - name: APP type: url-test proxies: [node-vless] url: http://cp.cloudflare.com/generate_204 interval: 180 tolerance: 50 rules: - MATCH,APP
要点:
rules只写MATCH,APP:这个实例只服务一个程序,不需要分流;加载 GEOSITE 规则集会在首次启动时下载几十 MB,离线还可能启动失败mixed-port必须和启动器里的$ProxyPort一致,改端口要两边一起改find-process-mode保持always,否则控制口看不到进程信息
5.2 三种配节点方式
方式一:手填单个节点
直接改 proxies: 段。注意字段名不能混:VLESS 用 servername,Trojan / AnyTLS 用 sni。写错不报错,但握手失败,表现为端口通了却 curl 000。
方式二:订阅链接(长期用这个)
proxy-providers: sub: type: http url: https://你的订阅地址 interval: 3600 path: ./providers/sub.yaml health-check: enable: true url: http://cp.cloudflare.com/generate_204 interval: 300 proxy-groups: - name: APP type: url-test use: [sub] url: http://cp.cloudflare.com/generate_204 interval: 180
手动触发更新(不用等 interval):
Invoke-RestMethod -Method Put -Uri 'http://127.0.0.1:59098/providers/proxies/sub'
注意:订阅必须是 Clash YAML 格式。base64 或 vmess:// 那种原始订阅,mihomo 吃不下,要先经过订阅转换。
方式三:从现成客户端搬
已在用 Clash / mihomo 系客户端(Clash Verge、FlClash 等)的,可以直接拷它的 proxies: 与 proxy-groups: 段,只改 mixed-port / DNS / 控制口三项。不要整份复制,它的 tun: 和规则会和本实例打架。
5.3 一个容易踩的格式坑
REALITY 公钥要求是 32 字节的 base64url,43 个字符,不带等号。带 = 的普通 base64 会被直接判 invalid REALITY public key,-t 校验就过不去。short-id 是 16 位以内的十六进制。
六、启动器
$ErrorActionPreference = 'SilentlyContinue' $ProxyDir = Join-Path $env:USERPROFILE '.codex\codex-proxy' $CoreExe = Join-Path $ProxyDir 'mihomo-codex.exe' $ProxyPort = 7898 $PkgName = 'OpenAI.Codex' $AppExeRel = 'app\ChatGPT.exe' $Aumid = 'OpenAI.Codex_2p2nqsd0c76g0!App' $Proxy = "http://127.0.0.1:$ProxyPort" function Test-Port($p) { try { $c = New-Object System.Net.Sockets.TcpClient $iar = $c.BeginConnect('127.0.0.1', $p, $null, $null) $ok = $iar.AsyncWaitHandle.WaitOne(400) -and $c.Connected $c.Close(); return $ok } catch { return $false } } # 1) 自己的内核没起就先起 if (-not (Test-Port $ProxyPort)) { Start-Process -FilePath $CoreExe -ArgumentList '-d', $ProxyDir -WindowStyle Hidden for ($i = 0; $i -lt 40; $i++) { Start-Sleep -Milliseconds 500 if (Test-Port $ProxyPort) { break } } } # 2) 定位 exe(MSIX 包目录每次更新都会变,必须动态取) $exe = $null $pkg = Get-AppxPackage -Name $PkgName | Select-Object -First 1 if ($pkg) { $c = Join-Path $pkg.InstallLocation $AppExeRel if (Test-Path -LiteralPath $c) { $exe = $c } } # 3) 关掉旧实例,新进程树才会继承新变量 Get-Process ChatGPT -ErrorAction SilentlyContinue | Stop-Process -Force Start-Sleep -Seconds 2 # 4) 只给本进程树注入代理,然后直接运行 exe if ($exe) { $env:HTTP_PROXY = $Proxy $env:HTTPS_PROXY = $Proxy $env:ALL_PROXY = $Proxy $env:NO_PROXY = 'localhost,127.0.0.1,::1' # --lang=zh-CN 固定界面/菜单语言,见第九节 Start-Process -FilePath $exe -ArgumentList @(('--proxy-server=' + $Proxy), '--lang=zh-CN') } else { # 兜底:exe 找不到就用 AUMID 激活(没有代理,但至少能开) Start-Process ('shell:AppsFolder\' + $Aumid) }
两个坑:
- 脚本注释和字符串只能用 ASCII。PowerShell 5.1 会把无 BOM 的 UTF-8 当 ANSI 读,中文注释会导致解析报错(
unexpected token) - 必须
Start-Process那个 exe,不能用 AUMID 激活,否则第 4 步注入的变量全部失效
静默包装 launch-codex.vbs(快捷方式指向它,不弹黑窗):
Set sh = CreateObject("WScript.Shell") ps1 = sh.ExpandEnvironmentStrings("%USERPROFILE%\.codex\codex-proxy\launch-codex.ps1") sh.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & ps1 & """", 0, False
建快捷方式(桌面 + 开始菜单):
$dir = Join-Path $env:USERPROFILE '.codex\codex-proxy' $pkg = Get-AppxPackage -Name 'OpenAI.Codex' | Select-Object -First 1 $ws = New-Object -ComObject WScript.Shell foreach ($t in @("$env:USERPROFILE\Desktop\Codex (代理).lnk", "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Codex (代理).lnk")) { $l = $ws.CreateShortcut($t) $l.TargetPath = 'C:\Windows\System32\wscript.exe' $l.Arguments = '"' + (Join-Path $dir 'launch-codex.vbs') + '"' $l.WorkingDirectory = $dir $l.IconLocation = (Join-Path $pkg.InstallLocation 'app\ChatGPT.exe') + ',0' $l.Save() }
以后只用这个快捷方式启动。原来的磁贴启动不带代理,症状就是 403 或一直转圈。
七、验证四步
$d = Join-Path $env:USERPROFILE '.codex\codex-proxy' # 1) 配置语法与节点自检(只校验,不监听) & "$d\mihomo-codex.exe" -t -d $d # 2) 走它测出口地区(最直接的一步) curl.exe -s -x http://127.0.0.1:7898 https://www.cloudflare.com/cdn-cgi/trace # 3) 用快捷方式起一次 Codex,然后确认子进程拿到了变量 Get-Process codex | Select-Object Id, StartTime # 4) 看它到底连去哪(有没有绕过代理) Get-NetTCPConnection -OwningProcess (Get-Process codex).Id | Select-Object RemoteAddress, RemotePort, State
正常结果:第 2 步输出里有 loc=US(或你节点所在地),第 4 步的远端地址全部指向代理端口,没有直连的公网 IP。
想省事可以把上面这些做成一个自检脚本,跑完打印 8 项 PASS/FAIL:端口是否监听、进程环境变量、连接数、泄漏连接数、出口地区、token 端点状态码、用户环境变量是否干净、登录凭据文件是否存在。
八、常见问题
| 症状 | 原因 | 处理 |
| — | — | — |
| 内核起来几秒就退出 | 端口或 DNS 端口被占 | 先跑 -t;换端口,DNS 也要换 |
| curl 走代理返回 000 | 节点参数错,或节点被墙 | 换节点;检查 servername 与 sni 是否写混 |
| curl 通,Codex 登录还是 403 | 用了磁贴启动,走了直连 | 只用代理快捷方式启动 |
| curl 通,Codex 一直转圈 | app-server 没拿到环境变量 | 查 codex 进程的 HTTPS_PROXY |
| 有连接绕过代理 | 少了 --proxy-server 参数 | 启动器加上该参数后重启程序 |
| 改了端口启动器没反应 | 只改了 config.yaml | config.yaml 与启动器两处同步改 |
| 订阅不更新 | 订阅链接失效或需 UA | 手动 Put 触发一次看报错 |
| 菜单栏一会儿中文一会儿英文 | 只配了 localeOverride,没给 Electron 传 --lang | 启动器加 --lang=zh-CN,见第九节 |
九、固定中文菜单与界面语言
现象:同一个代理快捷方式启动,界面有时中文、有时英文。
原因:Codex 桌面版的语言由两套互相独立的机制决定,只改一处就会飘。
| 层 | 决定什么 | 从哪里读 |
| — | — | — |
Electron 的 app.getLocale() | 顶部菜单栏(文件/编辑/视图/帮助) | 启动参数 --lang,不传就按系统/默认推断 |
config.toml 的 [desktop] localeOverride | 应用内部文案(新聊天、项目等) | 用户配置文件,空值表示自动探测 |
做法一:启动器追加语言参数(关键)
把启动器第 4 步的 Start-Process 改成:
Start-Process -FilePath $exe -ArgumentList @(('--proxy-server=' + $Proxy), '--lang=zh-CN')
--lang=zh-CN 是 Chromium / Electron 级别的开关,菜单栏语言跟着它走。只加这一个参数,重启后菜单栏就是中文。
做法二:配置文件也写死(防止应用内文案回退)
在 ~/.codex/config.toml 里加一行:
[desktop] localeOverride = "zh-CN"
[desktop] 段里通常已经有 followUpQueueMode / conversationDetailMode,把 localeOverride 加在同一段即可,不要再写第二个 [desktop]。
两条一起用才稳:
- 只加
--lang:菜单栏中文,但应用内文案仍可能按自动探测走英文 - 只写
localeOverride:应用内文案可能中文,菜单栏仍可能英文(实测如此) - 两条都写:菜单栏与界面正文统一中文
验证:完全退出 Codex(托盘图标也要退),用代理快捷方式重新启动,菜单栏应为 文件 编辑 视图 帮助,左侧栏为 新聊天 / 项目。
补充说明:
- 26.1016.8622.0 起设置里多了 Settings → General → Language,选「简体中文」等效于写
localeOverride,但不会改启动参数,所以启动器里的--lang=zh-CN仍建议保留 - 应用内文案是否加载还受服务端开关(Statsig 的
enable_i18n)影响。启动时那一次请求超时,正文可能回退英文,菜单栏不受影响;遇到「菜单中文、正文英文」,重启一次让它重新拉取即可 - 想换回英文:删掉启动器里的
--lang=zh-CN,并把localeOverride改成en-US(或直接删掉该行)
十、回滚
- 停内核:
Get-Process mihomo-codex | Stop-Process -Force - 删掉那两个快捷方式,Codex 就回到原样(磁贴启动、走直连)
- 全程没有改过系统代理,也没有写过用户环境变量,所以不需要额外清理
相关
- Windows 端 Claude 桌面版部署说明 —— 同一套思路的另一份实现(7899 端口)
本文整理自 2026 年 10 月 Windows 11 上的实操验证(Codex 版本 26.1016.8622.0,mihomo v1.19.32)。文中节点、域名、凭据均为示意值,请替换为自己的。整理录入:2026-10-23。