跳到主要内容
注册

Codex 报「unexpected status 401 Unauthorized」或「Missing environment variable」:原因与修复

这两条报错都和 Codex 发给 model provider 的 API Key 有关,只是出现在不同的时刻。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),配了 env_key 的自定义 provider 从 env_key 指定的环境变量里读取 Key;这个变量没设、为空或只有空白字符时,Codex 不发请求,直接打印以「Missing environment variable」开头、带变量名的提示。「unexpected status 401 Unauthorized」出现在请求发出之后,同一行里的 url 指出了是哪台服务器返回的 HTTP 401。url 是 https://api.router.one/v1/responses 时,说明 Router One 拒绝了这把 Key:Key 缺失或无效时,Router One 在调用任何模型之前就返回 401。用本页的 Router One provider 块时,在启动 Codex 的 shell 或编辑器里把 ROUTER_ONE_API_KEY 设成控制台 → API 密钥里当前有效的 Key,或者写进 ~/.codex/.env(Codex 启动时会加载它,其中的值优先于 shell 里的值),再重新启动 Codex。网关在中国大陆可直连,不需要 VPN。

两条报错分别是什么意思

在 Codex 的终端界面里,两条报错都显示为红色,开头是 ■。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),它们的组成如下,第一行带的是一键安装写入的 env_key_instructions:

Codex
■ Missing environment variable: `ROUTER_ONE_API_KEY`. Create an API key at https://router.one/dashboard/api-keys and set the ROUTER_ONE_API_KEY environment variable.
■ unexpected status 401 Unauthorized: <服务端的错误信息>, url: <Codex 请求的地址>, request id: <id>
  • 第一行写出 env_key 指定的变量名;provider 块里设了 env_key_instructions 时,后面再接这段说明。Codex 在准备请求时读取这个变量,值为空或只有空白字符也按没设处理,而且不会重试,所以这次请求根本没有发到任何服务器。
  • 第二行冒号后面是服务端自己的错误信息:JSON 错误正文里有 error.message 就用它,没有就原样打印正文(最多 1000 字节),正文为空时写 Unknown error。后面依次是 , url: …(Codex 请求的地址)、响应带 cf-ray 头时的 , cf-ray: …、响应带 x-request-id 头(没有时改读 x-oai-request-id)时的 , request id: …,以及响应带 x-openai-authorization-error 头、或 x-error-json 头里带错误码时的 , auth error: … 和 , auth error code: …。

Missing environment variable:设好 Codex 要读的变量

Codex 从 env_key 指定的变量里读取 Key,而 env_key 写在顶层 model_provider 选中的那个 [model_providers.<id>] 块里:按本页的 Router One 配置是 ROUTER_ONE_API_KEY,你自己的配置块写了别的名字,就是那个名字。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),Codex 读取启动时继承的环境变量,另外启动时还会加载 ~/.codex/.env(CODEX_HOME 下的 .env 文件),其中的同名变量会覆盖 shell 里的值。之后才设的、或者只在另一个终端里设的变量,它都看不到。先设好变量,再从带着这个变量的 shell 启动 Codex:

terminal
# macOS / Linux,当前终端:
export ROUTER_ONE_API_KEY="sk-your-router-one-key"
# 以后新开的终端(zsh;bash 改成 ~/.bashrc,macOS 上的 bash 改成 ~/.bash_profile):
echo 'export ROUTER_ONE_API_KEY="sk-your-router-one-key"' >> ~/.zshrc
# 检查是否已导出且不是空白(不打印 Key):
printenv ROUTER_ONE_API_KEY | grep -q '[^[:space:]]' && echo "已导出" || echo "没有导出,或者是空白"
# Windows PowerShell,当前窗口:
$env:ROUTER_ONE_API_KEY = "sk-your-router-one-key"
# 以后新开的窗口(保存为用户环境变量):
[Environment]::SetEnvironmentVariable("ROUTER_ONE_API_KEY", "sk-your-router-one-key", "User")
  • shell 配置文件在终端启动时读取:改完 ~/.zshrc、~/.bashrc 或 ~/.bash_profile,要新开一个终端,或者在当前终端里 source 一下。用 SetEnvironmentVariable 保存的变量只对之后新开的 PowerShell 窗口生效,所以当前窗口还要再用 $env: 设一次。
  • IDE 插件:按 Codex 的配置基础文档(2026-11-02 核对),CLI 和 IDE 插件共用同一套配置层,所以编辑器里同样需要这个变量;插件里的 Codex 用的是编辑器启动时的环境。设好变量后,把编辑器完全退出再重新打开。
  • ~/.codex/.env 由 Codex 自己读取,不受启动它的 shell 或编辑器影响:在里面写一行 ROUTER_ONE_API_KEY=sk-your-router-one-key,CLI 和编辑器里的 Codex 重新启动后都能读到。如果这个文件里已经有 ROUTER_ONE_API_KEY,Codex 用的就是那一行的 Key,要么保持更新,要么删掉。
  • provider 块里没有任何凭据设置时,Codex 根本不带 Key。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),没有打开 requires_openai_auth 的自定义 provider 只发送这个块自己配置的凭据:env_key 读到的 Key、experimental_bearer_token(Codex 配置参考不推荐这种写法),或者这个块的 auth、gateway_oauth 设置取得的令牌,不会用 codex login 保存的登录信息。请求不带 Key 时,Router One 返回 401。
  • 如果报错里是别的变量名,比如 OPENAI_API_KEY,说明你的会话用的 provider 块就是这么写的。接 Router One 时,给 Key 单独用一个变量,像下面的 Router One 配置那样用 ROUTER_ONE_API_KEY,免得读取 OPENAI_API_KEY 的其他工具把你的 Router One Key 拿去用。

unexpected status 401:先看 url

这一行里的 url 是请求发往的地址,看它就知道是谁返回的 401。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),这个地址由当前 provider 的 base_url 加上 /responses 组成;内置的 OpenAI provider 用 API Key 登录时是 https://api.openai.com/v1,用 ChatGPT 登录时是 https://chatgpt.com/backend-api/codex,除非 ~/.codex/config.toml 里的 openai_base_url 给了它别的地址,这时内置 provider 的凭据会发到那个地址,包括 ChatGPT 登录令牌。内置 provider 先走 WebSocket,所以它的 Reconnecting... 和回退提示里,同一个端点可能显示成 wss:// 开头的地址:

unexpected status 401:先看 url
报错里的 url说明什么怎么办
https://api.router.one/v1/responsesRouter One 拒绝了这把 Key。Key 缺失或无效(包括已吊销、已过期或不存在的 Key)时,Router One 在调用任何模型之前就返回 401,所以控制台 → 日志里没有这次请求的记录从控制台 → API 密钥重新复制 Key,确认它仍然有效、没有过期,把 ROUTER_ONE_API_KEY 设成它,再重新启动 Codex(见下一节)
https://api.openai.com/v1/responses会话走的是 Codex 内置的 OpenAI provider(API Key 登录),请求发到了 OpenAI 的 API,没有经过 Router One在 ~/.codex/config.toml 第一个表头之前写上 model_provider = "router",并配好下面那个 provider 块
https://chatgpt.com/backend-api/codex/responses会话走的是内置 provider 的 ChatGPT 登录,这个登录被拒绝了重新登录(先 codex logout 再 codex login),或者让本地会话改走下面的 Router One provider
其他地址当前 provider 块的 base_url,或者内置 provider 的 openai_base_url(内置 provider 的凭据会发到这个地址,包括 ChatGPT 登录令牌),指向了别处,比如别的服务,或者主机名写错了用 model_provider = "router" 选中 [model_providers.router] 块,并在块里写 base_url = "https://api.router.one/v1"

Router One 拒绝了 Key 时

url 是 https://api.router.one/v1/responses 的 401,问题出在 Codex 发出的 Key,和模型无关。逐项检查,然后重新启动 Codex:

  • Key 是当前有效的:它在控制台 → API 密钥里仍然存在、没有过期,而且变量里存的就是这把 Key,不是后来已经换掉的旧 Key。
  • 值里只有 Key 本身:连引号一起粘进去、在前面多写了 Bearer,或者 Key 中间混进空格,都会被拒绝。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),Codex 把变量的值原样放在 Bearer 后面;值里带换行时,这个值根本放不进 Authorization 头,请求就会不带 Key 发出去。
  • 必须是 Router One 的 Key:变量里放的是其他服务的 Key(比如 OpenAI API Key)时,Router One 同样返回 401;请到控制台 → API 密钥创建一把 Router One 的 Key。
  • ~/.codex/.env 里没有旧 Key:按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),Codex 启动时会加载这个文件,里面的 ROUTER_ONE_API_KEY 会覆盖 shell 里的值,所以旧的那一行要改掉或删掉。
  • 会话用的是 Router One 的 provider 块。如果 ~/.codex/config.toml 改用 openai_base_url 把内置的 OpenAI provider 指向 Router One,按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),Codex 发的是它自己的 OpenAI 登录凭据(通常是 codex login 保存的那份),不是 ROUTER_ONE_API_KEY:改用下面的 provider 块,或者用 codex login --with-api-key 以你当前的 Router One Key 登录,绝不要与 ChatGPT 登录同用。
  • 已经在运行的程序还拿着旧值:改变量之前就打开的编辑器或终端,发出的仍是旧 Key。
  • Router One 返回的 401 在控制台 → 日志里没有记录,因为网关在调用任何模型之前就拒绝了请求。如果 Key 一直被拒,写邮件给 support@router.one 时附上报错时间和报错行里的 request id(如果有),但不要附上 Key 本身。

不启动 Codex,先检查 Key

Router One 的接口参考里,GET /v1/models 对无效或缺失的 API Key 返回 401,所以用它就能按 Codex 发送 Key 的方式测试这把 Key,而不用调用模型。在启动 Codex 的那个 shell 里运行:它测试的是这个 shell 里的 ROUTER_ONE_API_KEY;如果 ~/.codex/.env 里也设了这个变量,Codex 发的是那里的值。它只会打印 HTTP 状态码:200 表示 Key 被接受,401 表示被拒绝。

terminal
curl -s -o /dev/null -w "%{http_code}\n" https://api.router.one/v1/models -H "Authorization: Bearer $ROUTER_ONE_API_KEY"
# Windows PowerShell:
curl.exe -s -o NUL -w "%{http_code}" https://api.router.one/v1/models -H "Authorization: Bearer $env:ROUTER_ONE_API_KEY"

用一键命令把 Codex 接到 Router One

带上你的 Router One Key 运行一键命令。它会在缺少 Codex CLI 时先安装,用 Router One 的配置替换 ~/.codex/config.toml(env_key = ROUTER_ONE_API_KEY,以及对应的 env_key_instructions;原有的文件保留为 config.toml.bak.<time>,需要的其他设置可以从里面复制回来),把 Key 写进 shell 配置文件(zsh 是 ~/.zshrc,bash 是 ~/.bashrc,其他 shell 是 ~/.profile),再发一个测试请求,Key 无效时会报 401。Windows 用 PowerShell 那一行,它会把 Key 存成用户环境变量。之后在新开的终端里,或者在安装之后才启动的编辑器里运行 codex。macOS 上用 bash 时,新开的终端窗口读的是 ~/.bash_profile:除非它已经加载 ~/.bashrc,否则把同一行 export 也加进去。

terminal
curl -fsSL https://router.one/install/codex.sh | bash -s -- sk-your-api-key
# Windows PowerShell:
$env:ROUTER_ONE_API_KEY = "sk-your-api-key"; irm https://router.one/install/codex.ps1 | iex

Router One 的 provider 配置

顶层的 model 和 model_provider = "router" 两行写在第一个表头之前,块里的 base_url、env_key、wire_api 三行照下面的样子保留,而且都写在 ~/.codex/config.toml 里:按 Codex 的高级配置文档(2026-11-02 核对),项目里的 .codex/config.toml 写了 model_provider 或 model_providers 会被忽略。这个 provider 不要打开 requires_openai_auth:按 Codex 认证文档(2026-11-02 核对),它会为这个 provider 打开 OpenAI 登录(用 ChatGPT 或 OpenAI API Key 登录),而 Router One 的 provider 用 env_key 读取 Key。一键安装写入的文件指向同一个 provider,另外加了 env_key_instructions,也就是 Codex 在缺少变量的提示后面打印的那段说明。

config.toml
# ~/.codex/config.toml
model = "gpt-5.6-sol"
model_provider = "router"

[model_providers.router]
name = "router"
base_url = "https://api.router.one/v1"
env_key = "ROUTER_ONE_API_KEY"
wire_api = "responses"

# 在 shell 里:
# export ROUTER_ONE_API_KEY=sk-your-router-one-key

常见问题

「unexpected status 401 Unauthorized」是 Router One 的报错吗?

这是 Codex 对 HTTP 401 的通用说法,不管 401 是哪台服务器返回的。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),Codex 先打印 unexpected status、HTTP 状态和服务端的错误信息,再打印它请求的 url。url 是 https://api.router.one/v1/responses 时,是 Router One 拒绝了 Codex 发出的 Key。用 Router One provider 块时,这把 Key 来自 ROUTER_ONE_API_KEY:把它设成控制台 → API 密钥里当前有效的 Key,再重新启动 Codex。如果是用 openai_base_url 把内置 provider 指向了 Router One,发出去的是 Codex 自己的 OpenAI 登录凭据(包括 ChatGPT 登录令牌),不是 ROUTER_ONE_API_KEY(见「Router One 拒绝了 Key 时」一节)。url 是别的地址,说明请求没有发到 Router One。

Codex 会重试 401 吗?

会,但每次发的都是同一把 Key。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),HTTP 层从不重试 401;Codex 的 401 处理可能会重新加载并刷新已保存的 ChatGPT 登录,但不会改动通过 env_key 读到的 Key。之后采样循环会和其他意外状态码一样重试这个请求:在 Router One provider 这类走 HTTPS 的 provider 上最多 stream_max_retries 次(默认 5 次),等待时间逐次拉长,所以报错之前可能先出现几行 Reconnecting...。内置的 OpenAI provider 先走 WebSocket,这几次重试用完后会回退到 HTTPS,可能再重试同样的次数。重试再多也修不好 Key:改好变量,再重新启动 Codex。

401 那一行冒号后面写着 invalid token、Incorrect API key provided 或 detail Unauthorized,是什么意思?

那是服务端自己的错误信息,不是 Codex 的。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),Codex 优先打印 JSON 错误正文里的 error.message,没有这个字段就原样打印正文(最多 1000 字节),正文为空时写 Unknown error,所以措辞取决于 url 对应的服务端。例如 OpenAI 的错误码文档(2026-11-02 核对)列出了 Invalid Authentication、Incorrect API key provided 等 401 错误;Router One 的接口参考里,示例 401 正文的错误信息是 invalid api key。正文形如 {"detail":"Unauthorized"} 时,说明服务端返回的就是这种格式,Codex 会原样显示。不管写的是什么,先看 url。

报错里的变量名是 OPENAI_API_KEY,或者是一个我从没设过的变量,为什么?

Codex 报的是会话所用 provider 块里 env_key 写的那个变量。按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),内置的 OpenAI provider 没有 env_key,所以缺少变量的提示一定来自某个自定义 provider 块:你自己写的、安装脚本写的,或者 Codex 的 profile 文件里的。检查 ~/.codex/config.toml 顶层的 model_provider 行,以及它指向的 [model_providers.<id>] 块。接 Router One 时,像本页的配置那样让 env_key 指向 ROUTER_ONE_API_KEY,并设好这个变量,免得读取 OPENAI_API_KEY 的工具用到你的 Router One Key。

变量已经设好了,VS Code 插件还是报 Missing environment variable,怎么办?

插件里的 Codex 用的仍是编辑器启动时的环境。按 Codex 的配置基础文档(2026-11-02 核对),CLI 和 IDE 插件共用同一套配置层,所以 Router One provider 在编辑器里同样需要 ROUTER_ONE_API_KEY。把编辑器完全退出(所有窗口都关掉)再重新打开。如果编辑器还是拿不到这个变量,就把 Key 写进 ~/.codex/.env,写成一行 ROUTER_ONE_API_KEY=sk-your-router-one-key:按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),Codex 启动时会自己加载这个文件,不管是谁启动了它;写好后再重启一次编辑器。

codex login --with-api-key 提示 API key saved,为什么还是 401?

按 openai/codex 源码(rust-v0.166.0,2026-11-02 核对),这个命令保存 Key 时并不校验它(提示原文是 API key saved. Authentication is checked when you send a request.),而且保存的 Key 属于内置的 OpenAI provider:没有打开 requires_openai_auth 的自定义 provider 从不发送它。如果会话用的是 Router One provider,起作用的是 ROUTER_ONE_API_KEY 里的 Key;如果用的是内置 provider,401 那一行的 url 会显示这把 Key 发到了哪里;用 openai_base_url 把它指向 Router One 时,用 codex login --with-api-key 以你当前的 Router One Key 重新登录,绝不要与 ChatGPT 登录同用。codex login status 只报告 Codex 自己的 OpenAI 登录状态,不读取 ROUTER_ONE_API_KEY,所以 Router One provider 能正常用时它也可能显示 Not logged in;codex logout 清除保存的登录信息。

在中国大陆能用这套配置吗?

能。网关在中国大陆可直连、无需 VPN,配置和在其他地区完全一样:同一份 config.toml,同一个 ROUTER_ONE_API_KEY 变量。遇到 401 也一样,先看 url,再查 Key。