1. 配置文件的三个层级与加载顺序:动手前必须知道的 TOML 真相
很多人第一次接触 Codex 本地自定义 Agent 时,第一反应是打开默认的~/.codex/config.toml直接改模型。这个思路没错,但如果你不知道这个文件在什么时候加载、被谁覆盖、能否按项目隔离,后面就会踩到“改了配置但完全没生效”“这个项目用的怎么还是旧模型”之类的坑。
Codex 的配置体系是我用过的 CLI 工具里比较典型的 TOML 三层结构:全局配置、项目配置、本地覆盖。默认情况下,你本机的配置文件在~/.codex/config.toml。如果你设置了CODEX_HOME环境变量,那么这个路径会跟着变化。命令行的--config参数可以指定一个完全自定义的配置文件路径,这在临时测试、CI 环境里非常有用。
三层配置的规则大概是这样:
- 全局配置(
~/.codex/config.toml):所有项目共享,放默认模型、默认的 model_provider、默认的权限策略。 - 项目配置(项目根目录下的
.codex/config.toml):新版本支持按项目隔离,适合团队统一某个仓库的模型和行为规范。 - 本地覆盖/命令行参数:优先级最高,适合临时切换模型、调试 provider。
实际测试下来,Codex 加载配置的顺序是“先全局、再项目、后命令行”,后面的配置会覆盖前面的同名配置项。也就是说,你在全局把model配成了gpt-5,项目里.codex/config.toml又写了model = "deepseek-chat",那这个项目实际跑的就是deepseek-chat。命令行如果再跟一个-m参数,那命令行说了算。
提示:别把环境变量和配置文件搞混。
OPENAI_API_KEY、CODEX_HOME这类环境变量是在 shell 启动时注入的,它们的生效时机比配置文件更早。如果同一个变量在配置文件的[env]块里也出现了,实测下来 shell 里已导出的值优先级更高,配置文件里的[env]更多是作为兜底。
1.1 一个最小可用的初始配置
先给一个最基础的配置骨架,你在改任何高级参数之前,先确保这个能跑通:
model = "gpt-5" model_reasoning = "gpt-5" model_reasoning_effort = "medium" [model_providers.openai] name = "OpenAI" base_url = "https://api.openai.com/v1" env_key = "OPENAI_API_KEY" wire_api = "responses"这个配置干了几件事:指定了默认模型和推理模型;声明了 OpenAI 官方 provider;通过env_key告诉 Codex 去哪里读取 API Key。注意我用的 key 是OPENAI_API_KEY,这个变量必须在 shell 里已经存在,否则 Codex 启动时会直接报“missing API key”。
1.2 profile:官方提供的配置隔离方案
如果你想在同一个机器上维护两套 Codex 配置,比如一套工作用、一套个人项目用,不需要频繁改config.toml,用profile是最干净的做法。配置里可以这样写:
[profiles.work] model = "gpt-5" model_reasoning_effort = "high" [profiles.personal] model = "deepseek-chat" [profiles.personal.model_providers.deepseek] name = "DeepSeek" base_url = "https://api.deepseek.com/v1" env_key = "DEEPSEEK_API_KEY" wire_api = "chat"然后运行时用codex --profile personal启动,或者设置环境变量CODEX_PROFILE=personal。profile 里的配置会覆盖顶层同名配置,这种“一个文件管多套环境”的方式比到处维护多个config.toml要省心得多。我自己的习惯是工作场景用work,写开源项目用personal,切换起来一条命令的事。
2. 把 Codex 接到任意模型:model_provider 与模型映射实战
Codex 默认只连 OpenAI 官方服务,但它的设计里有一个很关键的抽象层:model_provider。这个字段相当于告诉 Codex “你要发请求的服务端点长什么样”,把base_url、env_key、wire_api三样核心信息配好,Codex 就能连接到任何兼容 OpenAI 接口的服务。
2.1 三个核心字段的含义
先逐个拆解,这是很多人配置失败的重灾区:
base_url:API 服务的基础地址。注意,这里有个特别容易踩的坑:OpenAI 官方是https://api.openai.com/v1,但很多第三方兼容服务只写了https://api.deepseek.com,两者都能用,但 Codex 拼接请求路径时行为不一样。我建议统一带上/v1,这样路由前缀不容易 404。如果遇到“404 Not Found”或者“endpoint /responses 不存在”,十有八九是这个路径没对齐。env_key:指定从哪个环境变量读取 API Key。每个 provider 用独立的 key,比如DEEPSEEK_API_KEY,不要所有 provider 都共用一个OPENAI_API_KEY,否则切模型的时候会莫名其妙鉴权失败。wire_api:协议格式。OpenAI 官方现在推荐responses协议,但大多数第三方兼容服务只实现了老的chat协议(chat/completions)。如果第三方服务只支持 chat 协议,而你配成了 responses,Codex 启动时不会立刻报错,真正发消息的时候才会报 “failed while handling codex endpoint /responses”。这个坑我在下一节排错里详细讲。
2.2 用 DeepSeek 做例子:完整接入配置
热搜里“codex接入deepseek”出现频率很高,我直接用这个例子演示。第三方模型普遍是 OpenAI 兼容接口,接入步骤几乎一样。
model = "deepseek-chat" model_reasoning = "deepseek-reasoner" model_reasoning_effort = "high" [model_providers.deepseek] name = "DeepSeek" base_url = "https://api.deepseek.com/v1" env_key = "DEEPSEEK_API_KEY" wire_api = "chat"配置完成后,在 shell 里导出密钥:
export DEEPSEEK_API_KEY=sk-xxxx codex进交互界面后,Codex 就会用deepseek-chat处理对话,用deepseek-reasoner做深度推理。这里有一个容易忽略的点:model_reasoning_effort有三个档位low / medium / high,但它是否生效完全取决于上游模型是否支持这个参数。DeepSeek 的 reasoner 接受这个字段,但有些开源模型的网关会直接忽略它,不会有报错,只是行为上没什么区别。
2.3 model 与 model_reasoning 的分工逻辑
Codex 会把“普通对话”和“深度推理”拆成两个模型位。model负责日常生成,model_reasoning负责需要多步思考的任务。如果你只配置model不配置model_reasoning,Codex 默认会用同一个模型处理所有任务。如果只配model_reasoning不配model,Codex 可能会报 config 解析错误。
实际使用中我的建议是:预算有限就两个都配同一个模型,别让 Codex 因为缺少推理模型而频繁回退;预算充足就把推理模型配成更强的版本,日常对话用快一点的模型,体验会有明显区别。这里“快”和“强”的取舍完全看你的场景,写业务代码我倾向于快模型,做架构设计、重构老代码我切到强推理模型。
2.4 密钥管理的实操建议
不要在config.toml里明文写 key。配置文件会进版本库、会分享给同事,一旦泄露就是钱的问题。正确的做法是只写env_key,然后通过 shell 的.zshrc、.bashrc、或者 direnv 这类工具注入环境变量。如果你用 Git 管理~/.codex目录,记得把.env文件加进.gitignore。
3. AGENTS.md:用自然语言定义你的本地 Agent
模型配置解决的是“Codex 用哪个脑子思考”的问题,AGENTS.md 解决的是“Codex 按什么规矩干活”的问题。标题里的“本地自定义 Agent”,说白了就是用 AGENTS.md 把 Codex 从一个通用助手塑造成符合你项目习惯的专属 Agent。
3.1 全局 AGENTS.md 和项目 AGENTS.md 的分工
Codex 支持两个层级的 AGENTS.md,这一点和 config.toml 的层级思路是一脉相承的:
- 全局 AGENTS.md(
~/.codex/AGENTS.md):定义你所有项目的通用偏好。比如“所有代码注释使用中文”“提交信息遵循 Conventional Commits”“不要修改测试文件除非用户明确要求”。这些规则应该在每个项目里都生效。 - 项目 AGENTS.md(项目根目录 AGENTS.md):定义这个仓库特有的规则。比如“后端代码在
app/目录下”“数据库迁移文件放在migrations/”“前端组件统一使用 TypeScript 泛型”。
新版本的 Codex 还支持子目录级别的 AGENTS.md,也就是说你可以在src/modules/下再放一个 AGENTS.md,只约束那个目录里的代码。这个机制非常适合大型 monorepo,每个子模块的代码风格差异很大时,用子目录 AGENTS.md 做约束比在根目录堆一大堆规则要清晰得多。
3.2 一份高效的 AGENTS.md 应该写些什么
我见过很多人把 AGENTS.md 写成“你好,请遵守以下规则”这种废话开头的文档,一点用都没有。Codex 读 AGENTS.md 是把它当作额外的系统提示词上下文,不是读给你看的。所以内容要具备可执行性,每条规则都应该让 Agent 能直接判断对错。
我的写法分五块:
- 项目角色:一句话说清楚这个项目是什么,技术栈是什么。让 Agent 先建立背景认知。
- 目录地图:告诉 Agent 哪些目录是核心业务、哪些是工具脚本、哪些是自动生成不要动。这能避免它乱改不该改的文件。
- 硬性约束:不许做什么。比如“不要运行
npm install”“不要修改package-lock.json”“不要提交.env文件”。 - 工作流偏好:希望 Agent 完成任务时遵循什么顺序。比如“先读
docs/architecture.md再动手”“每次修改后必须跑npm test”。 - 示例代码风格:贴一小段你觉得写得好的代码。Codex 的模式匹配能力很强,给它一个“好例子”比给它十条抽象规则更管用。
示例片段:
# Project: Payment Service You are working in a NestJS-based payment service. - Core business logic lives in `src/domain/` - HTTP controllers live in `src/api/` - Never modify files under `generated/` - After changing a service, run `npm test` and make sure all tests pass - Use the existing logging format: `logger.info('message', { ctx })`3.3 AGENTS.md 和 config.toml 的边界
这是很多人会混淆的一点。config.toml 管的是“模型、权限、沙盒、环境变量”,AGENTS.md 管的是“上下文、规则、行为偏好”。两者不是二选一,而是互补。比如“这个项目用哪个模型”应该写进 config.toml;“这个项目不要动哪些目录”应该写进 AGENTS.md。如果你在 AGENTS.md 里写模型名,在 config.toml 里写编码规范,系统不会报错,但维护起来非常拧巴。
我见过最离谱的一个配置是,用户把 API Key 直接写在 AGENTS.md 里,理由是“让 Agent 自己读”。这个做法不仅有泄露风险,而且完全没必要,config.toml 的 env_key 机制就是干这个事的。记住:AGENTS.md 永远不应该包含敏感信息。
4. 优先级规则全解析:冲突发生时听谁的
配置项多了以后,最头疼的问题就是优先级。Codex 文档里其实有明确的优先级顺序,但我发现很多人根本没仔细看,导致“我明明在这里改了配置,为什么没生效”。这一节我把容易发生冲突的场景全部列出来,按我的实测结果给一个可以直接参考的排序。
4.1 配置来源的优先级排序
从高到低整理如下:
| 来源 | 优先级 | 典型场景 |
|---|---|---|
| 命令行参数 | 最高 | codex -m model_name、codex --profile work |
| 环境变量 | 高 | CODEX_PROFILE、CODEX_HOME、OPENAI_API_KEY |
| 项目级 config | 中 | .codex/config.toml中的 model 配置 |
| 全局 config | 低 | ~/.codex/config.toml中的默认配置 |
| 内置默认值 | 最低 | Codex 出厂自带的 model_provider 设置 |
这个顺序决定了排查问题的思路:如果某个配置没生效,先从命令行参数查起,再看环境变量是不是被设置了,最后才怀疑项目级和全局配置写错了。
4.2 模型选择优先级:默认模型、快速模型与推理模型
模型字段之间也有优先级关系。model是兜底模型,model_reasoning是增强模型。当任务被 Codex 判定为“需要深度推理”时,它会优先使用model_reasoning;否则用model。这个判定机制不透明,但你可以通过model_reasoning_effort的档位控制它的触发倾向。档位越高,Codex 越倾向于把任务当作复杂任务处理,代价是响应时间变长、token 消耗变大。
我实测的一个经验:model_reasoning_effort设成low时,代码生成速度明显变快,但复杂的跨文件重构容易“敷衍”;设成high时,简单问题也会被过度思考,经常多写很多没必要的代码。日常开发建议用medium,需要慢工出细活的时候临时用--config或者-m切换到 high。
4.3 AGENTS.md 的优先级陷阱
AGENTS.md 的优先级遵循“越靠近当前工作目录的指令越权威”的原则。也就是说,子目录 AGENTS.md 会覆盖项目根目录 AGENTS.md 里的同名规则,项目根目录会覆盖全局~/.codex/AGENTS.md。
这个机制看起来合理,但它有一个隐藏陷阱:Codex 实际上是把所有匹配到的 AGENTS.md 内容都塞进上下文,而不是简单“后者覆盖前者”。也就是说,如果全局 AGENTS.md 里写了“禁止修改生成的代码”,而某个子目录 AGENTS.md 写了“可以修改generated/目录下的模板文件”,两条规则会同时存在,Agent 可能会根据上下文强弱随机选择遵守哪一条。
建议:同一个规则只在一个层级定义。如果子目录确实需要例外,在最靠近的 AGENTS.md 里写清楚“覆盖全局规则,允许做 XX”,用这种显式声明减少歧义。
4.4 一个可复用的优先级自查表
我把平时排查配置问题时用的清单整理成表格,遇到“配置没生效”直接对照查:
| 症状 | 第一检查项 | 第二检查项 | 第三检查项 |
|---|---|---|---|
| 模型没切换 | codex -m参数 | CODEX_PROFILE环境变量 | profile 里的 model 字段 |
| API Key 不生效 | shell 是否导出同名 key | config.toml 里的 env_key 名字 | [env] 块是否写错 |
| AGENTS.md 规则不生效 | 是否存在更高优先级 AGENTS.md | 内容是否是可执行指令 | 是否因为太长被截断 |
| wire_api 报错 | provider 是否支持 responses | base_url 路径是否带 /v1 | 是否拼错协议类型 |
5. 高频报错与排错思路:从“无法发送消息”到“沙盒更新”
配置写完了,跑起来难免会遇到报错。我把这段时间在社区里看到的高频问题,以及我自己排查过的几个典型案例整理在这一节。这部分内容的价值不在于报错本身,而在于排查思路。
5.1 codex 无法发送消息:从密钥到网络层逐层排查
“无法发送消息”是个非常笼统的报错,很多人一看到就以为网络出问题了,其实大部分时候是密钥和 base_url 的问题。我的排查顺序是固定的:
- 先看是不是密钥问题:
echo $DEEPSEEK_API_KEY,确认变量存在。如果变量为空,查~/.zshrc或.bashrc里的 export 语句,新开的 shell 才会加载最新配置。 - 再看 base_url 是否正确:用 curl 直接打一下接口,看看返回什么。比如 DeepSeek 的话,
curl https://api.deepseek.com/v1/models -H "Authorization: Bearer $DEEPSEEK_API_KEY",能返回模型列表说明网络层和鉴权都没问题。 - 最后开 debug 模式:
codex --debug会输出完整的请求细节、HTTP 状态码和响应体。这一步能精确看到 Codex 到底往哪个 URL 发了什么请求。
5.2 cc switch local proxy failed:工具链切换配置失效的典型场景
最近很多人在用 ccswitch 这类工具统一管理多个 AI CLI 的配置,因为它们可以快速在 Claude Code、Codex 之间切换模型和服务商。我遇到的一个典型报错是启动 Codex 时提示 “failed while handling codex endpoint /responses”,这个报错看起来像网络问题,但排查下来其实是配置切换后前后两套配置残留导致的。
原因通常是这三类:
- wire_api 不匹配。切换工具时把 provider 的 wire_api 带过来了,比如 Claude Code 的配置模板用的是
chat协议,Codex 不同版本默认期望responses,两边没对齐,发请求时 Codex 命中了一个不存在的/responses端点。 - base_url 路径没带 /v1。这是路由 404 的头号原因,Codex 会在 base_url 后面拼具体路径,你没带
/v1,它拼出来的地址自然不对。 - 环境变量残留。切换配置后,新 provider 的
env_key改成读取DEEPSEEK_API_KEY,但 shell 里还残留着上一个 provider 的OPENAI_API_KEY,两个 key 在同一个环境里,Codex 可能会选错。
排查建议很简单:先用env | grep -i api_key看看当前环境里到底有哪几个 key,再检查切换后的配置里base_url和wire_api是否和实际服务端匹配。大部分“local proxy failed”类报错都能通过这两步定位。
5.3 显示更新 agent 沙盒:沙盒权限问题
Codex 的沙盒机制是用来限制 Agent 对文件系统的访问的。启动时提示“更新 agent 沙盒”或者操作时提示没有权限写入,通常和sandbox_mode配置有关。默认的沙盒模式是只读,Codex 只能读取工作区内容,不能写文件。如果你需要它改代码、创建文件,得把沙盒模式调到允许写入:
sandbox_mode = "workspace-write"workspace-write允许 Codex 在当前项目工作区内写文件,但不允许碰工作区以外的目录(比如家目录里的其他项目)。如果你确认某个任务需要更大范围的权限,Codex 也提供danger-full-access模式,但我不建议日常使用,一旦 prompt 注入或者指令错误,后果不可控。
“显示更新 agent 沙盒”这个提示还有一层含义是 Codex 的沙盒镜像或者版本过旧需要更新。这种情况一般直接运行安装脚本更新 Codex 本体,正常情况下沙盒组件会跟着一起升级。
5.4 排错方法论:不要瞎试,要有最小复现
我发现很多人在排错时有个坏习惯:看到一个报错,就在配置文件里东改一下西改一下,改完重启一遍,不行再改别的。这种“瞎试”方法效率非常低,因为多个变量同时变化后,你根本不知道是哪个改动生效了。
我的方法是二分回滚:保留一份绝对能跑的配置(比如官方默认配置),在这个基础上只加一个自定义项,跑一次看是否成功。如果失败,回滚这个改动,换下一个。这样每一步的因果关系都清晰。遇到复杂问题时,再用codex --debug抓请求日志,对照日志里的 URL、状态码、响应体来定位,而不是靠猜。
这份配置排查的经验,比我一开始说的“记住优先级顺序”更值钱。因为优先级表是死的,真实环境里配置来源的组合方式是无穷的,只有掌握了“最小复现 + 分级排查”的方法,才能在各种奇奇怪怪的报错面前不慌。
我自己现在维护 Codex 配置的方式很简单:所有配置文件纳入 Git 管理,每次变更都提交;每个项目单独写 AGENTS.md,不依赖全局规则;尽量用 profile 隔离不同场景,避免在同一个 config.toml 里堆太多互相可能冲突的配置。这套组合拳打下来,Codex 在本地真正变成了一个“知道这个项目该怎么干活”的专属 Agent,而不是一个每次都要从零解释上下文的新实习生。