1. Codex auth.json 报 401 的真实场景:本地鉴权文件到底存了什么
Codex CLI 在本地跑起来之后,很多人第一次遇到 401 都不是在代码里,而是在终端里敲完一句 prompt 之后,屏幕上直接甩出一行401 Unauthorized或者OAuth token refresh failed。这个报错的根源,八成不在你的网络,也不在模型本身,而在~/.codex/auth.json这个文件里。
先说清楚auth.json是什么。Codex CLI 是 OpenAI 官方出的命令行编码代理,它需要知道两件事:第一,请求发到哪个 endpoint;第二,用哪个凭据去鉴权。这两件事在旧版本里靠环境变量拼,在新版本里统一收敛到了auth.json。你可以把它理解成 Codex 的「身份证 + 通讯录」——身份证是 API Key 或 OAuth token,通讯录是它要拨号的地址。当这个文件里的 endpoint 指向了一个已经失效的地址,或者 token 过期后 refresh 流程走不通,Codex 就会在第一次请求时直接 401,连模型都还没碰到。
为什么这个场景在 2026 年变得特别常见?因为 Codex CLI 的鉴权模型在这两年改过好几轮。早期版本用OPENAI_API_KEY环境变量就能跑,后来引入了 OAuth 登录,再后来把配置拆成了config.toml管行为、auth.json管凭据。很多人的auth.json是几个月前生成的,里面的 refresh token 早就轮换失效了,但 Codex 启动时不会主动告诉你「你的 token 过期了」,它只会在真正发请求时抛 401。更麻烦的是,有些人的auth.json里 endpoint 还指向旧的直连地址,而那个地址在当前网络环境下已经不可达,于是报错信息里混着local proxy failed和401,让人以为是两个问题,其实是一个。
我试过在一台放了三个月的开发机上直接跑 Codex,结果就是OAuth refresh failed: invalid_grant。当时第一反应是重新登录,但codex login走的是浏览器回调,在无头环境或者远程 SSH 里根本弹不出浏览器。这时候正确的做法不是反复登录,而是把auth.json的 endpoint 和凭据统一指向一个稳定的 API 通道,让 Codex 用 API Key 模式而不是 OAuth 模式工作。这也是这篇要交付的核心动作:把auth.json迁移到 TaoToken 的统一 Key/API 通道,用可复制的配置片段替换掉那个已经失效的鉴权文件。
适合谁看?三类人。第一类是本机 Codex CLI 突然 401、想快速恢复编码的人;第二类是在 CI 或远程容器里跑 Codex、没法走浏览器 OAuth 的人;第三类是想把 Codex 的请求统一收口到一个可观测、可切换模型的 API 通道、方便做成本和质量对比的人。这三类人的共同点是:他们不需要理解 OAuth 的完整协议栈,只需要一个能复制粘贴、改完就能验证的auth.json。
在动手之前,有一个原则必须先立起来:改auth.json之前一定先备份。这个文件里可能有你唯一的 refresh token,一旦覆盖错了,原来的登录态就找不回来了。备份命令很简单,cp ~/.codex/auth.json ~/.codex/auth.json.bak,一行就够。后面所有的修改都基于备份可回滚的前提来做。下面进入具体的接入准备。
2. TaoToken 前置准备:拿到统一 Key 与确认 endpoint 形态
在改auth.json之前,你需要先准备好两样东西:一个可用的 API Key,和一个明确的 Base URL。这两样都从 TaoToken 的控制台拿。打开 https://taotoken.net/api 可以看到 API 的入口说明,而 Key 的生成在控制台里完成,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite 。进去之后创建一个新的 API Key,复制出来先存到安全的地方,这个 Key 只会完整显示一次。
这里要区分两个概念:官网首页和 API 入口。官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,用来了解整体能力;API 入口是 https://taotoken.net/api ,不带任何 UTM 参数,是给程序调用的。你在auth.json里填的 Base URL 应该是 API 入口这一层,而不是首页。很多人第一次配错就是把首页地址填进了 endpoint,结果请求打到了 HTML 页面上,返回一堆非 JSON 内容,Codex 解析失败后报的错看起来像鉴权问题,其实是地址层级错了。
关于 Key 的权限,建议按最小可用原则来。如果你只是本地跑 Codex 做编码,创建一个普通调用权限的 Key 就够了,不需要开管理权限。Key 的命名建议带上用途和日期,比如codex-local-20260819,这样后面在控制台看用量时能一眼对上。如果你有多台机器,不要共用同一个 Key,每台机器一个,出问题时能快速定位是哪台在异常调用。
Base URL 的形态要特别注意。TaoToken 的 API 入口是https://taotoken.net/api,但在 Codex 的配置里,endpoint 通常需要写到能拼出/v1/chat/completions或/v1/responses的层级。也就是说,你在auth.json里填的 base 应该是https://taotoken.net/api,Codex 会在后面自动拼接路径。如果你填成了https://taotoken.net/api/v1,有些版本会拼成/api/v1/v1/...,直接 404。这个坑我在早期配置时踩过,报错是unexpected status 404,但混在 401 的日志里很容易被忽略。
还有一个前置动作是确认你的 Codex 版本。不同版本的auth.json字段名不完全一样。用codex --version看一下,如果是 0.2x 之后的版本,auth.json里通常有OPENAI_API_KEY、tokens、last_refresh这几个字段。老版本可能只有api_key。你可以在改之前先cat ~/.codex/auth.json | python -m json.tool把结构打印出来,看清楚有哪些字段,再决定怎么替换。这一步花不了一分钟,但能避免改完之后 Codex 因为字段不识别而静默忽略你的配置。
最后,如果你打算长期用 Codex 做编码代理,而不是临时跑一次,建议同时了解一下 Coding Plan 的形态,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite 。它和按量调用的 Key 是两种不同的计费与配额模型,长期高频编码场景下选对模式能省不少事。前置准备到这里就够了,接下来进入真正的配置环节。
3. 可复制配置:auth.json 与 config.toml 的完整片段
这一节是全文的核心,所有片段都可以直接复制,只需要把 Key 替换成你自己的。先说文件位置。Codex 的配置目录默认在~/.codex/,里面有两个关键文件:auth.json管凭据,config.toml管模型和行为。在 Windows 上路径是%USERPROFILE%\.codex\,在 macOS 和 Linux 上是~/.codex/。如果你用的是容器,注意这个目录要挂载出来,否则每次重建容器登录态就丢了。
先备份,再改。备份命令:
cp ~/.codex/auth.json ~/.codex/auth.json.bak cp ~/.codex/config.toml ~/.codex/config.toml.bak然后是auth.json的目标结构。把下面这段里的sk-你的TaoTokenKey替换成你在控制台生成的真实 Key:
{ "OPENAI_API_KEY": "sk-你的TaoTokenKey", "tokens": null, "last_refresh": null }这里的关键动作是把tokens置为null,把last_refresh也置为null。为什么?因为 Codex 在启动时会检查tokens字段,如果它非空,Codex 会优先走 OAuth refresh 流程,而不是用OPENAI_API_KEY。你之前遇到的OAuth refresh failed就是这条路径触发的。把tokens清空,等于告诉 Codex「不要走 OAuth,直接用 API Key」。这是整个迁移里最关键的一步,很多人改完还是 401,就是因为tokens字段没清干净,Codex 仍然在尝试刷新那个已经失效的 token。
接下来是config.toml。这个文件决定 Codex 请求发到哪个 endpoint、用哪个模型。片段如下:
model = "gpt-5.6-sol" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "OPENAI_API_KEY" wire_api = "chat"逐行解释。model是你要用的模型 ID,这里写的是示例,你可以换成 TaoToken 支持的任意模型 ID,具体以模型对话页面列出的为准,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite 。model_provider指向下面定义的 provider 名。base_url填https://taotoken.net/api,注意不要带尾斜杠,也不要带/v1。env_key告诉 Codex 从哪个环境变量读 Key,这里写OPENAI_API_KEY,和auth.json里的字段名对应。wire_api用chat,对应 chat completions 协议;如果你的 Codex 版本支持 responses 协议且你想用,可以改成responses,但初次迁移建议先用chat,兼容性最好。
三件套在这里的对应关系是:Base URL 是https://taotoken.net/api,Key 是auth.json里的OPENAI_API_KEY,Model ID 是config.toml里的model。这三个值必须同时正确,缺一个都会失败。我见过有人只改了auth.json没改config.toml,结果请求还是发到旧 endpoint,报 401;也有人只改了config.toml没清tokens,结果 OAuth 流程先跑,报 refresh failed。所以这两个文件要一起改,改完一起验证。
如果你用的是 CC Switch 这类多配置切换工具,逻辑是一样的,只是把上面两个片段分别填到它对应的 auth 和 config 槽位里。CC Switch 的好处是可以在多个 provider 之间快速切换,适合同时用多个模型通道的人。但无论用不用切换工具,底层落到磁盘上的还是这两个文件,理解它们的结构比记住某个工具的界面更重要。
改完之后,用python -m json.tool ~/.codex/auth.json验证 JSON 语法没写错,用cat ~/.codex/config.toml确认 TOML 没有拼写错误。JSON 里多一个逗号、TOML 里少一个引号,都会让 Codex 启动时静默回退到默认配置,然后你看到的还是 401,但原因已经变成了配置文件解析失败。这一步的检查成本极低,收益极高。
4. 验证请求:从 codex exec 到成功返回的完整自检
配置改完不等于生效,必须发一次真实请求验证。最直接的验证方式是codex exec,它跑一次非交互式的单轮请求,适合做自检。命令如下:
codex exec "用一句话说明什么是快速排序"如果配置正确,你会看到 Codex 把请求发出去,然后返回一段模型生成的文本。这时候去看终端输出里有没有 endpoint 相关的日志。有些版本会打印Using provider: taotoken和POST https://taotoken.net/api/v1/chat/completions,看到这两行基本就说明路由对了。如果只看到401或者OAuth refresh failed,说明auth.json的tokens字段没清干净,回到上一节检查。
更细一点的自检可以用 curl 直接打 API,绕过 Codex 本身,确认 Key 和 endpoint 是通的:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoTokenKey" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.6-sol", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 16 }'如果这条 curl 返回了正常的 JSON,里面有choices字段,说明 Key 和 endpoint 都没问题,问题就锁定在 Codex 的配置解析上。如果 curl 返回 401,说明 Key 本身有问题,去控制台确认 Key 是否被禁用或删除。如果 curl 返回 404,说明路径拼错了,检查是不是多写了/v1。这个「先 curl 再 codex」的顺序能帮你快速二分定位问题在通道还是在客户端。
成功返回的典型特征是这样的:codex exec输出里有模型回复,没有error字段,退出码是 0。你可以用echo $?看退出码。如果退出码非 0,即使屏幕上打印了一段看起来像回复的文本,也可能是错误信息被当成了输出。这一点在脚本里跑 Codex 时特别重要,不要只看 stdout,要看退出码。
验证通过之后,建议再跑一次带上下文的请求,确认多轮对话也正常:
codex exec "写一个 Python 函数,输入列表返回去重后的列表"这次观察返回内容是否是代码,以及有没有被截断。如果返回的是代码但格式乱了,可能是wire_api设成了responses而模型不支持,改回chat再试。如果返回内容为空但退出码是 0,检查max_tokens是不是被设得太小,或者模型 ID 写错了导致返回了空 choices。
对于在 CI 里跑 Codex 的场景,验证方式要改成非交互式加超时:
timeout 60 codex exec "输出 OK" || echo "codex failed with $?"这样即使 Codex 卡住,60 秒后也会退出,不会把整个流水线挂死。CI 环境里还要确保~/.codex/auth.json是通过 secret 注入的,而不是提交到仓库里。Key 泄露的风险在 CI 里比本地高得多,因为日志可能被公开。
验证通过后,把这次成功的配置记录下来,包括 Codex 版本、模型 ID、Base URL。下次再出问题时,这份记录能帮你快速判断是配置漂移还是通道故障。到这里,一次完整的迁移就算跑通了。接下来是排障环节,把最常见的几个报错逐个拆开。
5. 常见报错排查:401、local proxy failed 与 reading choices
第一个高频报错是401 Unauthorized。在auth.json迁移场景里,401 有四种可能。第一种,tokens字段没清空,Codex 仍在走 OAuth refresh,refresh 失败后回退到空 Key,于是 401。解法是把tokens和last_refresh都置为null。第二种,auth.json里的OPENAI_API_KEY和config.toml里的env_key对不上,比如auth.json写的是OPENAI_API_KEY,config.toml写的是TAOTOKEN_KEY,Codex 读不到 Key,发出去就是无鉴权请求。解法是让两边的字段名一致。第三种,Key 本身在控制台被禁用或额度耗尽。解法是去控制台看 Key 状态。第四种,Key 复制时带了空格或换行,JSON 里看起来正常但实际值多了字符。解法是用python -c "import json;print(repr(json.load(open('auth.json'))['OPENAI_API_KEY']))"打印出来看有没有多余空白。
第二个高频报错是local proxy failed或connection refused。这个报错和鉴权无关,是网络层的问题。常见原因是config.toml里的base_url写成了一个本地代理地址,比如http://127.0.0.1:8080,但那个代理没启动。迁移到 TaoToken 之后,base_url应该是https://taotoken.net/api,不需要任何本地代理。如果你之前配过本地代理做转发,现在要把它去掉,否则 Codex 会先连本地代理,代理没起来就报这个错。另一个原因是 DNS 解析失败,用curl -v https://taotoken.net/api看能不能解析到 IP,解析不了就是本机 DNS 问题,和 Codex 无关。
第三个高频报错是error reading choices或missing choices field。这个报错说明请求发出去了、也返回了,但返回的 JSON 结构里没有choices字段,Codex 解析失败。原因通常是base_url层级错了,请求打到了首页或者某个非 API 路径,返回的是 HTML 或错误 JSON。检查base_url是不是https://taotoken.net/api,有没有多写/v1或少写/api。另一个原因是wire_api设成了responses,但返回的是 chat completions 格式,字段名对不上。改回chat再试。还有一种少见情况是模型 ID 写错了,服务端返回了一个错误对象而不是正常响应,Codex 把它当成了响应体去解析。
第四个报错是OAuth refresh failed: invalid_grant。这个报错在迁移前最常见,迁移后如果还出现,说明auth.json里还有残留的tokens结构没清干净。有些版本的auth.json里tokens是一个嵌套对象,包含access_token和refresh_token,你只把外层置 null 可能不够,要把整个tokens键删掉或者确保它是null。用python -m json.tool打印完整结构确认。
第五个报错是model not found或invalid model。这个和鉴权无关,是config.toml里的model值不在服务端支持的列表里。去模型对话页面确认可用的模型 ID,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite ,把model改成列表里存在的值。注意模型 ID 大小写敏感,GPT-5.6-Sol和gpt-5.6-sol可能被当成两个不同的模型。
排障的通用方法是分层验证:先 curl 验证通道,再 codex exec 验证客户端,最后看日志定位是哪一层。不要一上来就改配置,先确定问题在哪一层,能省掉大量反复试错的时间。如果 curl 通了但 codex 不通,问题一定在 Codex 的配置解析;如果 curl 都不通,问题在 Key 或网络,和 Codex 无关。
6. 长期使用建议与接入文档入口
迁移完成之后,有几件事值得长期做。第一,把auth.json和config.toml纳入版本管理时一定要排除 Key,用.gitignore把~/.codex/auth.json排除掉,只提交一个脱敏的模板文件。第二,Key 定期轮换,尤其是在多台机器共用或者 CI 环境里用过之后,轮换成本很低,但能显著降低泄露风险。第三,给 Codex 的请求加一个简单的用量观测,TaoToken 控制台能看到调用量,定期看一眼有没有异常峰值,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite 。
如果你在迁移过程中遇到本文没覆盖的报错,接入文档里有更完整的字段说明和示例,地址是 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite 。文档里对auth.json各字段的含义、config.toml的 provider 配置、以及不同 Codex 版本的差异都有说明。遇到reading choices这类解析错误时,文档里的响应格式示例能帮你快速判断是客户端配置问题还是服务端返回问题。
对于需要管理多个 Key 的场景,API Keys 页面可以集中查看和吊销,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite 。建议每台机器、每个 CI 环境用独立的 Key,命名带环境和日期,这样吊销时不会误伤其他环境。如果发现某个 Key 异常,直接吊销再生成新的,比排查泄露源更快。
最后,如果你打算把 Codex 用在长期的编码代理工作流里,而不是偶尔跑一次,可以看一下 Coding Plan 的配额模型,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=codex_auth_json&utm_campaign=rewrite 。它和按量调用的区别在于配额是预置的,适合每天都有稳定编码量的场景。选哪种取决于你的使用频率,偶尔用按量更划算,天天用预置配额更省心。
回到最开始那个 401。它的本质不是「你的账号有问题」,而是「Codex 在用一个已经失效的鉴权路径」。把auth.json的 endpoint 和凭据指向 TaoToken 的统一通道,清掉残留的 OAuth token 结构,再用 curl 和 codex exec 两层验证,这个问题就能稳定解决。整个过程的核心动作只有三个:备份、替换、验证。备份保证可回滚,替换保证路径正确,验证保证真的生效。这三步做完,你的 Codex 就能重新跑起来,而且下次再遇到类似问题时,你知道该看哪个文件、该改哪个字段。