Codex远程与双VPN小记
今天来记一件很烦人、但是搞清楚之后又挺顺理成章的小事:
本机要开北大 VPN 连集群,同时又要用 Clash 让 Codex 访问 ChatGPT;远程环境里的会话动不动就断。
场景大概是这样的:
- 本机 Mac:Ivanti 连
vpn.pku.edu.cn,才能 SSH 到校内机器(比如xeon6) - 本机还开着 Clash Verge,不然 ChatGPT / Codex 基本用不了
- Codex 桌面端连着远程项目(工作目录在集群上)时,报错长这样:
stream disconnected before completion:
error sending request for url (https://chatgpt.com/backend-api/codex/responses)
2
本地项目会话有时是好的,远程一开就「正在重新连接」。人很裂。
# 问题到底出在哪
先把两条路拆开看,别混在一块:
| 你在干什么 | 流量实际从哪出去 | 需要谁 |
|---|---|---|
| SSH / 改远程代码 / 跑集群命令 | 本机 → 北大 VPN → 校内机 | Ivanti |
| Codex 调 ChatGPT API | 发请求的那一侧 → chatgpt.com | 能稳定出网的代理(Clash) |
这里有个容易误会的点:桌面连着远程项目时,API 请求经常是在远程机器上发的,不是你以为的「全在本机发」。
所以远程会话挂了,很多时候不是「Codex 坏了」,而是:
xeon6 想访问 chatgpt.com
→ 集群自己出网?不太行 / 很不稳
→ 或者经 SSH 隧道回到 Mac,再从 Mac 出网
→ Mac 默认路由已经被 Ivanti 抢走了
→ ChatGPT 流量跟着钻进校园网 → 超时 / 流式中途断开
2
3
4
5
再叠一层 Clash 的 TUN:DNS 可能变成 198.18.x.x 这种 Fake-IP,SSH 到校内机时你会看到奇奇怪怪的地址,VPN 图标还显示「已连接」。那不是 VPN 没连上,是 Clash 把流量劫持走了。
一句话:VPN 负责进校园,Clash 负责出 ChatGPT;两条路抢同一个默认出口时,长连接最先死。
# 试过但不太行 / 容易把本机搞乱的做法
这些坑我或多或少都踩过,写出来免得别人再绕:
狂改 Clash 的 TUN / 全局路由 / PAC
能缓解一阵,但和 Ivanti 叠在一起时,本机网络很容易变得「不知道谁在接管」,回滚也麻烦。只开 TUN,以为提示里写了「无须系统代理」就稳了
Clash 那句是理想情况。macOS 上 ChatGPT / Codex 这种 Electron 应用,TUN 不一定抓得全;再叠加 DNS 污染,直连会打到奇怪 IP 上一直SYN_SENT。远程写
HTTP_PROXY=http://127.0.0.1:7897
7897 是本机 Clash 端口。远程机器上的127.0.0.1是远程自己,不是你的 Mac。除非 Clash 装在远程,否则这个地址是错的。SSH 动态反向代理:
ssh -R 17890
能把 SOCKS 听到远程,但流量回到 Mac 之后仍走系统默认路由。VPN 一开,ChatGPT 还是可能进校园网。短请求偶尔通,流式一长就断。把本机网络改得「很正确」但不可恢复
能用,但下次你只想正常上网时会哭。后面那套方案刻意把改动面收小。
# 最后比较稳、也相对好恢复的做法
思路很朴素:让远程 Codex 固定走「隧道 → 本机 Clash」,别让它自己瞎出网。
远程 Codex / codex-cli(xeon6)
→ socks5h://127.0.0.1:17890
→ SSH 反向转发到本机
→ 本机 Clash 混合端口(例如 7897)
→ 机场节点
→ chatgpt.com
2
3
4
5
6
对应两处改动:
# 1)本机:隧道接到 Clash,而不是动态 SOCKS
不要用裸的:
ssh -R 17890 xeon6
而是:
ssh -R 17890:127.0.0.1:7897 xeon6
含义是:远程连 127.0.0.1:17890,等于连到你 Mac 上的 Clash。
可以用一个小 keeper 挂着自动重连;挂掉了再拉起来就行。
# 2)远程:给 Codex 指到 17890
例如在远程写 ~/.codex/.env(没有就新建):
ALL_PROXY=socks5h://127.0.0.1:17890
HTTPS_PROXY=socks5h://127.0.0.1:17890
HTTP_PROXY=socks5h://127.0.0.1:17890
NO_PROXY=127.0.0.1,localhost,::1,.pku-dasys.cn,.local,.pku.edu.cn
2
3
4
然后重启一下远程的 codex app-server(或重开桌面远程会话),让进程吃到新环境变量。
自检可以很粗暴:
# 本机
nc -z 127.0.0.1 7897 && echo clash_ok
# 远程
curl -x socks5h://127.0.0.1:17890 -o /dev/null -w '%{http_code}\n' \
-X POST https://chatgpt.com/backend-api/codex/responses \
-H 'content-type: application/json' -d '{}'
# 看到 401/400 一类「到达了服务端」的响应,通常就说明链路通了
2
3
4
5
6
7
8
流式通不通,比只测一个 HTTP 码更关键;短请求通、长 SSE 断,多半还是中途路由/代理不稳。
# 为什么这样就会稳一些
- 出口唯一:ChatGPT 固定进 Clash,不再跟 Ivanti 默认路由缠斗
- 和本机能用的翻墙路径一致:节点、规则都复用你已经验证过的 Clash
- 改动面小:主要动「SSH 转发目标 + 远程代理环境变量」,不必把本机系统代理/TUN 大卸八块
- 远程用
codex exec/ tmux 挂任务,也走同一条代理链
当然它不是魔法:本机 Clash 关了、隧道断了、机场节点挂了,远程照样会挂。这是依赖关系,不是玄学。
# 其他可能的路(各有代价)
在集群上直接装 Clash / mihomo
最干净:远程自己出网,不依赖本机隧道。前提是机器能访问机场、你有权限常驻进程。很多校内机做不到。需要远程时关 Clash TUN,只留系统代理;或反过来
能减少 Fake-IP 坑,但双开场景下仍要小心默认路由。能拆角色就拆
只 SSH 时开 VPN;只在本机用 Codex 时开 Clash。简单,但做不到「一边连集群一边让远程会话稳定调 API」。同步会话记录
本地和远程的~/.codex/sessions/默认不共享。想在远程看到本机旧对话,得自己rsync,不是代理问题。模型本身过载
链路通了仍可能失败,例如服务端server_is_overloaded。那和 VPN/Clash 无关,换个模型或过会儿再试。
# 以后再遇到时,优先按这个顺序排
- 本机 Clash 混合端口还在听吗?
ssh -R 17890:127.0.0.1:7897这类转发还在吗?(不是裸-R 17890)- 远程
ALL_PROXY/~/.codex/.env是否指向socks5h://127.0.0.1:17890? - 远程
curl经 17890 能否打到chatgpt.com/backend-api/codex/responses? - 再考虑动 TUN / 系统代理 / 订阅规则——那是扩大爆炸半径的操作。
写这篇不是为了安利某套神配置,只是把「本地 VPN + Clash + 远程 Codex」这坨第一次理顺时的路径记下来。
下次再断,先别急着重装宇宙,先看隧道有没有接到 Clash。