折腾了一晚上,各种方案都试了,最后发现根子不在系统代理,也不完全在 Node.js,而在 OpenClaw 的 OAuth token exchange 调用没有显式启用环境变量代理。
结论先放前面:OpenClaw 的 fetchWithSsrFGuard 默认是 STRICT 模式,不会自动读取 HTTP_PROXY / HTTPS_PROXY。 这会导致 https://auth.openai.com/oauth/token 这个请求直接裸连,在受限网络环境里自然会失败。
现象
执行 OpenAI Codex OAuth 登录:
| |
系统层面的代理是通的,浏览器和 curl 也没问题,但 OpenClaw 在 OAuth 回调后的 token exchange 阶段失败。
一开始很容易以为是 Node.js 22 原生 fetch() 不走代理的问题。这个判断不算错,但还不够具体。真正影响这次调用的是 OpenClaw 自己封装的 fetchWithSsrFGuard。
试过但没用的方案
| 方案 | 失败原因 |
|---|---|
NODE_OPTIONS="--require proxy-fix.js" + undici ProxyAgent | preload 无法稳定影响 fork 出来的子进程 |
| socks5 代理 | 原生 fetch 不认 socks5 环境变量 |
global-agent bootstrap | 对 Node.js 原生 fetch 无效 |
手动 curl 换 token | PKCE verifier 在进程内存里,外部拿不到 |
proxychains | 需要额外安装和 sudo,不想把问题复杂化 |
这些方案都在外围绕圈。真正应该看的,是 OpenClaw 代码里这次请求到底怎么发出去的。
根本原因
问题集中在 OAuth token exchange:
| |
OpenClaw 内部调用 fetchWithSsrFGuard 时,带了类似这样的审计上下文:
| |
但没有传:
| |
也就是说,即使外部设置了:
| |
这次请求也不会自动走代理。
更坑的是,fetchWithSsrFGuard 还有 DNS pinning 相关保护。要允许环境变量代理,需要同时传:
| |
所以根因可以拆成三层:
- Node.js 22 原生
fetch()默认忽略HTTPS_PROXY/HTTP_PROXY环境变量 - OpenClaw 的
fetchWithSsrFGuard默认使用STRICT模式,不走代理 - OAuth token exchange 请求直连 OpenAI,被受限网络环境拦截
这不是单纯的 Node.js bug,更准确地说,是应用层代码没有为这个网络环境显式启用代理。
临时解决方案:改源码
我最后直接改了全局安装目录里的 OpenClaw 编译后文件。
先说明:这是临时补丁。升级 OpenClaw 后可能会被覆盖,最好还是等上游在 OAuth 流程里正式支持代理参数。
| |
然后重新执行登录:
| |
秒过。
关键点
- OpenClaw 的
fetchWithSsrFGuard支持proxy: "env"参数 - OpenAI Codex OAuth token exchange 流程里没有传这个参数
- 设置
HTTP_PROXY/HTTPS_PROXY只是必要条件,不是充分条件 - 在这个场景下,还需要
dangerouslyAllowEnvProxyWithoutPinnedDns: true绕过 DNS pinning 限制 - 这是 OpenClaw 代理适配的问题,不是 Node.js 自己能自动解决的问题
教训
代理问题最容易让人误判,因为它横跨系统环境、运行时、HTTP 客户端和应用封装。
这次最大的教训是:别只盯着环境变量,要看代码实际怎么发请求。
以后遇到类似问题,我会先做两件事:
grep目标域名或请求上下文,定位调用点- 看 HTTP 封装是否显式支持代理、是否真的传了代理参数
很多时候,HTTPS_PROXY 设对了,请求还是裸连。不是代理没生效,而是应用层根本没打算读它。