Codex 桌面端使用第三方 API 时,如何让浏览器扩展真正可用
关键词:Codex Desktop、第三方 Provider、DeepSeek、ChatGPT 浏览器扩展、Computer Use、nodeRepl.fetch、代理
一、问题现象
环境:Windows 11 + Codex 桌面端 26.908.40834,模型走第三方 Provider(DeepSeek),本机用 Clash 提供本地 HTTP 代理 127.0.0.1:7890。
表现是:模型对话、代码任务一切正常,但只要调用浏览器能力就失败。报错按修复进度依次出现过三种,正好对应三层互不相干的故障:
Codex auth token is unavailablenodeRepl.fetch request failedUnable to load browser request-header policy. Retry the browser command.
这三条不是同一个问题的不同措辞,而是三个独立故障被依次揭开。下面逐层说明。
二、第一层:Codex auth token is unavailable
原因
Codex 桌面端里其实有两套彼此独立的认证:
| 用途 | 认证来源 |
|---|---|
| 模型推理 | 第三方 Provider 的 token |
| 浏览器 / Computer Use 等插件能力 | ChatGPT 账号登录态(auth.json) |
第三方 API Key 只能证明你有权调用那个模型,不能代替 ChatGPT 账号态。当 Provider 配置里写的是 requires_openai_auth = false 时,Codex 的 app-server 在 getAuthStatus 里会返回:
{"authMethod": null, "authToken": null, "requiresOpenaiAuth": false}
浏览器运行时拿不到令牌,于是就报 Codex auth token is unavailable。
错误的做法
直觉上会想把 requires_openai_auth 改成 true 就完事。但如果同时把第三方 token 手写在 http_headers.Authorization 里,会出现认证头互相覆盖的风险——最坏的情况是 ChatGPT 的 bearer token 被发到第三方模型端点,属于凭据外泄,绝对不能这么配。
正确的双轨配置
把第三方 token 放在 Provider 专属的 experimental_bearer_token 字段里,再打开 requires_openai_auth:
model_provider = "custom"
model = "deepseek-v4-flash"
[model_providers.custom]
name = "deepseek"
base_url = "https://api.deepseek.com"
wire_api = "responses"
requires_openai_auth = true
experimental_bearer_token = "<你的第三方 API Key>"
关键点:不要再手写 [model_providers.custom.http_headers] Authorization。
为什么这样不会把 ChatGPT 令牌发给第三方?见 Codex 官方源码 codex-rs/core/src/client.rs:
fn uses_codex_backend(&self, auth: Option<&CodexAuth>) -> bool {
let provider = self.state.provider.info();
auth.is_some_and(CodexAuth::uses_codex_backend)
&& provider.is_openai()
&& provider.requires_openai_auth
&& provider.env_key.is_none()
&& provider.experimental_bearer_token.is_none()
&& provider.auth.is_none()
&& provider.aws.is_none()
}
只要 Provider 配了 experimental_bearer_token,模型请求就继续用第三方 token,账号凭据不会被挂上去;requires_openai_auth = true 只是让插件那一路能拿到 ChatGPT 账号态。
验证方法
# 确认是 ChatGPT 登录,而不是 API Key 登录
codex login status
# 期望输出:Logged in using ChatGPT
想直接确认 app-server 是否愿意发放令牌,可以手动起一个 app-server 并用 JSON-RPC 问它:
codex app-server --listen stdio://
然后依次发送:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"probe","title":"probe","version":"0.1.0"}}}
{"jsonrpc":"2.0","id":2,"method":"getAuthStatus","params":{"includeToken":true}}
修复后应返回 authMethod: "chatgpt" 且带真实令牌;修复前 authMethod 是 null。注意 includeToken 这个参数:不传它,authToken 字段会是 null。
三、第二层:nodeRepl.fetch request failed
第一层修好后,报错变成 nodeRepl.fetch request failed,每次卡大约 21 秒才超时。这一步和认证已经无关了。
原因
浏览器控制后端需要访问 OpenAI 的端点来获取浏览器身份信息,而本机在特定网络环境下,直连这些端点是被阻断的。实测对比(curl):
直连 https://chatgpt.com/ -> Connection was reset(curl 35)
经 127.0.0.1:7890 代理 -> HTTP 403(链路可达,403 是服务端策略)
直连 https://api.openai.com/v1/models -> 12 秒超时
经 127.0.0.1:7890 代理 -> HTTP 401(链路可达,401 是缺凭据)
也就是说:只有让后端显式使用本地 HTTP 代理,这一步才能过。开着 TUN 模式之所以能正常,本质也是把所有流量强制导进了代理。
顺带一个容易走错的方向:~/.codex/config.toml 里的 [mcp_servers.node_repl.env] 改了没用。应用启动时会重写这个文件,改动会被抹掉。同理,插件目录下的 .mcp.json 也会被重新物化,改了同样会被覆盖。
有效的做法
真正的生效位置是拉起 node_repl 的启动脚本:
%LOCALAPPDATA%\OpenAI\Codex\runtimes\cua_node\<hash>\bin\node_modules\@oai\cua-repl\dist\lib\js\oai_js_cua_repl\src\launch.js
它里面用 spawn 拉起 node_repl.exe,env 是 {...process.env, ...}。在这个对象里补上代理变量即可:
// 原样:Object.assign(Object.assign({}, n.env), { CUA_REPL_ENABLED_SURFACES: ...
// 改成:
Object.assign(Object.assign({}, n.env), {
NODE_USE_ENV_PROXY: "1",
HTTP_PROXY: "http://127.0.0.1:7890",
HTTPS_PROXY: "http://127.0.0.1:7890",
http_proxy: "http://127.0.0.1:7890",
https_proxy: "http://127.0.0.1:7890",
NO_PROXY: "localhost,127.0.0.1,::1",
no_proxy: "localhost,127.0.0.1,::1",
CUA_REPL_ENABLED_SURFACES: ...
})
改完先做语法检查,再完全退出并重启 Codex:
node --check launch.js
NO_PROXY 里保留 localhost,127.0.0.1,::1 是必要的,否则本地回环的桥接请求会被绕进代理。
改完之后,nodeRepl.fetch request failed 消失,报错推进到第三层。
四、第三层:Unable to load browser request-header policy
这一层来自 browser-service.mjs 里的 Statsig 初始化逻辑:
if (await t.initialization, !(await Ck(t))?.success || t.client.loadingStatus !== "Ready")
throw new Error("Unable to load browser request-header policy. Retry the browser command.");
它要取的特性开关是 codex_browser_use_agent_request_header。先排除网络因素:featureassets.org、api.statsig.com、prodregistryv2.org 直连与走代理均可响应,所以不是网络问题。
这一层是时序问题:Statsig 客户端初始化需要一点时间,刚重启完应用就去调浏览器能力会命中它。等一会再重试即可,不需要任何额外配置。实测重启后第一次调用失败,稍后重试就成功了。
如果反复重试仍然失败,那属于上游 bug。同版本已有多个公开报告,且官方订阅环境下同样复现,这类情况只能等应用更新。
五、最终验证
三层都通了之后,用只读方式验证最可靠。下面这段代码可以直接在 Codex 的 JavaScript 运行时里跑:
const mod = await import(
"file:///<你的插件路径>/browser/26.908.40834/scripts/browser-client.mjs"
);
const rt = await mod.setupBrowserRuntime({ environment: "codex-app" });
// 1) 看有哪些浏览器后端
const list = await rt.browsers.list();
console.log(JSON.stringify(list));
// 2) 读取已打开标签页(只读,不点击不提交)
const edge = list.find((b) => b.type === "extension");
const eb = await rt.browsers.get(edge.id);
const tabs = await eb.tabs.list();
for (const t of tabs) console.log(t.title + " | " + t.url);
// 3) 新建标签页并导航
const tab = await eb.tabs.new({ url: "https://example.com" });
console.log(await tab.title(), await tab.url());
预期结果:能列出内置浏览器与 Edge 两个后端、能读到已打开的标签页、能新建并导航。实测输出类似:
Example Domain | https://example.com/
另外,聚合接口 cua.getState() 之前会跟着报错,三层修好后它也会一起恢复正常。
六、几个容易踩的坑
- provider 切换工具会清掉 ChatGPT 登录态。 例如 cc-switch 这类工具,如果「保留官方登录」开关没打开,切换 Provider 时会顺手把
auth.json删掉。清了之后浏览器能力会立刻失效,表现就是又回到第一条报错。 - 密钥明文落盘。
experimental_bearer_token会把 Key 明文写在config.toml里,建议把配置文件权限收紧。 - 补丁会被覆盖。
launch.js在应用更新或运行时重新物化后可能被还原;config.toml和插件.mcp.json则几乎每次启动都会被重写。更新应用后如果浏览器能力又坏了,先检查这几处。 - 代理端口要对得上。 上面写死的
7890要和本机代理监听端口一致,换了端口记得同步修改。 - 平台层 fallback 只解决"打开"。 直接用系统调用打开 Edge 页面可以做到,但读不到页面内容、也点不了按钮,那类操作必须依赖扩展桥。
七、小结
整个过程的核心判断是:先把「模型认证」和「插件认证」两套链路分开,再按报错逐层推进。
Codex auth token is unavailable:requires_openai_auth = true+experimental_bearer_token,双轨认证nodeRepl.fetch request failed:在launch.js的 spawn 处注入代理环境变量,让后端走本地代理Unable to load browser request-header policy:Statsig 初始化时序,稍等重试即可
只要记住「报错变了就是进展了」这条线索,这类多层故障其实很好拆——每一层的报错信息都会明确指向下一个待解的问题。
更多推荐



所有评论(0)