1. 为什么本地跑 Codex harness 总卡在接入层
Codex 开源 harness 是 OpenAI 放出的编码 agent 运行时,Apache-2.0 协议,核心引擎已经用 Rust 重写,仓库里codex-rs/目录承载了绝大部分逻辑,codex-cli/只剩一层很薄的 Node 包装。它能做什么?简单说,它把「模型对话」和「本地文件读写、命令执行」串成一个可编排的回合循环,你给它一个任务,它自己决定读哪个文件、跑哪条命令、改哪一行。适合谁?适合想把编码 agent 嵌进自己工作流、CI 流水线,或者单纯想搞明白 agent 运行时到底怎么组织的人。
但真正动手的人会发现,harness 本身跑起来不难,难的是接入层。auth.json放哪、字段叫什么、Base URL 怎么指、模型 ID 从哪来,这几件事一旦错一个,表现就是各种看不懂的报错:要么 401,要么local proxy failed,要么流式响应里reading choices直接断掉。我见过太多人卡在这一步,以为是 harness 有 bug,其实是配置没对齐。
这篇就聚焦接入层,把auth.json和 Base URL 的配置拆开讲,给可复制的片段,最后用一个真实请求验证链路。你跟着做,能跑通一次完整的模型调用,确认 harness 到模型服务这条链路是活的。核心检索词就三个:Codex、harness、开源接入配置。读完你应该能自己判断「是配置问题还是网络问题」。
先说清楚一个前提:Codex harness 的接入层设计是「认证信息」和「服务端点」分离的。认证走auth.json,端点走环境变量或配置文件里的 Base URL。这两者必须同时正确,缺一不可。很多人只改了 Base URL 却忘了auth.json里的 key,或者反过来,结果就是反复 401。
2. TaoToken 前置:把 Key 和端点准备好
在动 Codex harness 之前,你得先有一个能用的模型服务端点和一个 API Key。这里我用 TaoToken 作为示例端点,因为它同时提供 OpenAI 兼容接口和 Claude Code 兼容接口,对 Codex 这种走 OpenAI 协议风格的 harness 比较友好。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后进控制台拿 Key。
拿 Key 的路径是:登录后进 console,找到 API Keys 页面,新建一个 Key。这个 Key 就是后面要写进auth.json的东西。注意,Key 只在创建时完整显示一次,复制下来存好。如果你用的是团队账号,确认一下这个 Key 有没有绑定到你想用的模型组。
端点方面,TaoToken 的 API 基址是 https://taotoken.net/api ,注意这个地址不带任何查询参数,是纯粹的 Base URL。Codex harness 里配置 Base URL 时,通常需要的是「到/v1之前」的那一段,具体取决于 harness 版本对路径的拼接方式。这一点后面配置章节会展开。
模型 ID 这块,你需要确认自己要用哪个模型。Codex harness 默认会读配置里的 model 字段,如果你不指定,它可能用一个内置默认值,而那个默认值在你的端点上未必存在。所以最稳的做法是显式写死模型 ID。TaoToken 的模型列表可以在模型对话页面或者文档里查到,选一个你账号有权限的。
这里有个容易踩的坑:有些人把 Key 直接写进 shell 的export OPENAI_API_KEY=xxx,然后指望 harness 自动读。Codex harness 确实支持读环境变量,但它的读取优先级和字段名在不同版本里变过。为了可复现,我建议统一走auth.json,把环境变量作为兜底而不是主路径。这样你换机器、换 shell 都不会因为忘了 export 而失败。
另外提醒一句,auth.json里存的 Key 是明文,别把它提交到 git。放到~/.codex/下面,并且确认这个目录不在任何仓库的追踪范围内。团队协作时,用.gitignore把auth.json和config.toml都排除掉,只提交一份脱敏的示例文件。
3. 可复制配置:auth.json 与 Base URL 拆解
这一节是重点,给可直接复制的片段。先明确目录约定:Codex harness 默认读~/.codex/下的配置。auth.json和config.toml都放这里。如果你用CODEX_HOME环境变量改了目录,那下面的路径要相应替换。
先看auth.json。它的结构在不同版本略有差异,但核心字段是固定的。下面这份是实测可用的最小结构:
{ "OPENAI_API_KEY": "sk-你的TaoToken密钥", "OPENAI_BASE_URL": "https://taotoken.net/api" }注意两点。第一,字段名是OPENAI_API_KEY和OPENAI_BASE_URL,不是api_key或base_url。Codex harness 读的是这套大写蛇形命名。第二,OPENAI_BASE_URL这里填的是不带/v1的基址,harness 内部会自己拼/v1/chat/completions或/v1/responses。如果你填成https://taotoken.net/api/v1,很可能拼出/api/v1/v1/...这种重复路径,直接 404。
然后是config.toml。这个文件管的是 harness 的行为,包括模型选择、沙箱模式、审批策略。接入层相关的关键片段如下:
model = "你选定的模型ID" model_provider = "openai" [sandbox] mode = "workspace-write" [approval] policy = "on-request"model_provider = "openai"这行告诉 harness 走 OpenAI 兼容协议,这样它才会去读auth.json里的OPENAI_BASE_URL。如果你不写这行,某些版本会走默认 provider,可能忽略你的 Base URL。
如果你更习惯用环境变量而不是auth.json,等价配置是:
export OPENAI_API_KEY="sk-你的TaoToken密钥" export OPENAI_BASE_URL="https://taotoken.net/api"但如前所述,环境变量在子进程、CI 环境里容易丢,auth.json更稳。两者同时存在时,auth.json优先级更高。
关于模型 ID,这里给一个判断方法:如果你不确定端点上有哪些模型,先用模型对话页面手动发一条消息,看它返回的模型名,或者查文档里的模型列表。把那个确切的字符串填进model字段。别用模糊匹配,harness 不做模型名归一化。
还有一个细节:config.toml里的键名是 snake_case,比如model_provider而不是modelProvider。这个和auth.json的大写风格不一样,别混。我见过有人把auth.json写成小写,结果 harness 读不到,报 401,查了半天以为是 Key 失效。
配置写完,用cat ~/.codex/auth.json确认一下内容,注意别在共享屏幕上暴露 Key。然后就可以进下一步验证了。
4. 验证请求:跑一次确认链路可用
配置对不对,跑一次就知道。Codex harness 提供了无头模式codex exec,适合做链路验证,因为它不启动 TUI,输出直接打到 stdout,方便看结果。
最简验证命令:
codex exec --json "用一句话说明当前目录下有哪些文件"这条命令会做几件事:读auth.json拿 Key 和 Base URL,读config.toml拿模型和 provider,然后向https://taotoken.net/api/v1/...发一个请求,把模型返回的内容以 JSON 形式打出来。
如果链路通,你会看到类似这样的输出结构:
{"type":"message","role":"assistant","content":"当前目录下有 README.md、src、package.json 等文件。"}具体字段名可能因版本而异,但关键是你能看到 assistant 的回复内容,而不是错误对象。看到内容,说明认证、端点、模型三件事都对了。
如果你想更直接地验证 Base URL 和 Key,可以绕过 harness,用 curl 打一发:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "你选定的模型ID", "messages": [{"role": "user", "content": "ping"}] }'这个 curl 能通,说明 Key 和端点没问题,那 harness 再报错就一定是 harness 配置的问题,排查范围立刻缩小。这是个很实用的二分法:先用 curl 确认服务侧,再用codex exec确认 harness 侧。
验证时注意看 HTTP 状态码。200 是通,401 是 Key 问题,404 是路径拼接问题,429 是限流。把这几个码记住,后面排错直接对号入座。
跑通之后,你可以把codex exec接进脚本,比如在 CI 里做一次冒烟测试,确认每次部署后接入层还是活的。命令加--json就是为了方便脚本解析。
5. 常见错排查:401、local proxy failed、reading choices
这一节按真实报错来对。你大概率会遇到下面几类。
第一类,401 Unauthorized。报错原文通常是{"error":{"message":"Incorrect API key provided","type":"invalid_request_error"}}。原因有三个可能:Key 写错了、Key 没写进auth.json而是只写了环境变量但没生效、或者auth.json字段名写成了小写。排查顺序:先cat ~/.codex/auth.json看字段名是不是OPENAI_API_KEY,再看值有没有多余空格或换行。如果都对,用第 4 节的 curl 直接测 Key,curl 也 401 就是 Key 本身的问题,去 console 重新生成一个。
第二类,local proxy failed或connection refused。这个报错说明 harness 尝试连一个本地代理端口,但那个端口没服务。常见原因是你的环境里设了HTTP_PROXY或HTTPS_PROXY环境变量,harness 继承了它,于是把请求发到了本地代理。解决办法是检查并清掉这些变量:
unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy然后重跑codex exec。如果你确实需要走代理,那要确保代理服务在跑,且能转发到taotoken.net。但多数情况下,直连就够了,多余的代理变量只会添乱。
第三类,reading choices相关报错,比如error reading choices: unexpected end of JSON input。这个通常出现在流式响应场景,说明 harness 在解析 SSE 流的时候,收到的数据不完整或者格式不对。可能原因:Base URL 填错导致返回了 HTML 错误页而不是 JSON、模型 ID 不存在导致端点返回了非预期结构、或者网络中断。排查:先用 curl 非流式打一发,确认返回的是标准 JSON;再检查OPENAI_BASE_URL有没有多写/v1;最后确认模型 ID 拼写。
第四类,OAuth 相关报错。如果你看到OAuth token expired或failed to refresh token,说明 harness 在读某个 OAuth 凭证而不是你的 API Key。这通常是因为auth.json里混入了 OAuth 字段,或者 harness 版本默认走了 OAuth 流程。解决办法是清空auth.json,只保留OPENAI_API_KEY和OPENAI_BASE_URL两个字段,删掉任何tokens、oauth之类的键。
把这几类报错和对应动作整理成一张表,方便你对照:
| 报错关键词 | 最可能原因 | 第一步动作 |
|---|---|---|
| 401 / Incorrect API key | Key 错或字段名错 | 检查 auth.json 字段名与值 |
| local proxy failed | 代理环境变量干扰 | unset 所有 proxy 变量 |
| reading choices | Base URL 或模型 ID 错 | curl 验证端点返回 JSON |
| OAuth token expired | auth.json 混入 OAuth 字段 | 只保留两个核心字段 |
排查的核心思路是二分:先用 curl 确认服务侧,再用 harness 确认客户端侧。两边都通,链路就通。
6. 接入之后:把配置固化下来
链路跑通只是开始,真正省事的是把配置固化。我的做法是把~/.codex/auth.json和~/.codex/config.toml做成模板,换机器时直接复制,只改 Key 和模型 ID 两个值。模板里OPENAI_BASE_URL固定填 https://taotoken.net/api ,这样端点不会写错。
如果你要在多台机器上同步,别把 Key 提交到仓库。用一个本地脚本在首次运行时提示输入 Key 并写入auth.json,脚本本身可以提交。这样既省事又不泄露。
另外,Codex harness 的配置键名演进比较快,你升级版本后如果突然报配置错误,先对照你装的版本的官方 config 参考,确认键名没变。接入层的两个核心字段OPENAI_API_KEY和OPENAI_BASE_URL相对稳定,但 provider 相关的键可能会调整。
最后给一个实用技巧:把验证命令写成一个 shell 函数,每次改完配置跑一下,几秒钟确认链路。比如:
codex_check() { codex exec --json "reply with ok" 2>&1 | head -5 }输出里能看到 assistant 内容就是通,看到 error 就按第 5 节排查。这个习惯能帮你把接入层的问题挡在真正干活之前。