一、config.toml
更新时间:2026-07-26。基于 OpenAI Codex Manual、OpenAI Codex GitHub 源码、Codex CLI
0.146.0-alpha.3.1,以及本地~/.codex/config.toml实践整理。文中的“当前配置”是这个日期的本机快照;本文只讲本地 Codex App / CLI / IDE Extension 会读取的配置,不讲云端 Codex 任务的后台环境配置。其中tui.*只作用于 CLI 的终端界面,不代表 App 或 IDE 的通知行为。
config.toml 是 Codex 本地客户端的持久默认配置。是告诉 Codex:默认用什么模型、请求发给哪个 provider、命令执行权限多大、启用哪些 MCP / plugin / 本地偏好。
可以先把它理解成 Codex 启动时会读取的一组默认值:
- 模型请求:
model、model_provider、model_providers.*、model_reasoning_effort。 - 执行权限:
sandbox_mode、approval_policy,以及新版default_permissions/[permissions.*]。 - 外部工具:
mcp_servers.*、plugins.*、features.*。 - 自动动作:
notify、hooks.*。 - 本地体验:
tui.*、desktop.*、项目 trust 状态。
它不负责三件事:
- 登录态本身:ChatGPT 登录、API key 存储、OAuth token 通常在
auth.json、系统钥匙串或环境变量里。 - 项目写作规则:仓库里的
AGENTS.md是给 Agent 的行为指导,不是config.toml。 - 第三方中转服务本身:
config.toml只告诉 Codex 怎么连中转;中转平台如何鉴权、如何把请求转给后端模型,是中转服务自己的事。
用户级配置文件更准确的定位方式是:
$CODEX_HOME/config.tomlCODEX_HOME 未显式设置时通常是 ~/.codex,所以常见路径是 /Users/markz/.codex/config.toml。启动器或发行版也可以设置不同的 CODEX_HOME;例如当前 ducx 环境使用 /Users/markz/.baidu-cx/config.toml。可以先看当前 shell 有没有显式设置它:
printenv CODEX_HOME没有输出通常表示回退到 ~/.codex,但这只能证明当前 shell 的环境。如果是通过启动器、别名或 App 进入 Codex,应在相同的启动环境中执行:
codex doctor --json报告里的 checks.config.load.details.CODEX_HOME 和 config.toml 会给出该次运行实际解析的路径;即使认证或网络检查导致总体状态为 fail,仍可以单独看 config.load 结果。启动器如果只给子进程注入环境变量,就要通过同一启动器调用 doctor,或用完全相同的 argv 和环境重现;单独在另一个 shell 里运行 printenv 不足以确认。
下面的顶层字段专指 /Users/markz/.codex/config.toml 这份本机快照,不是 ducx 的 .baidu-cx 配置:
sandbox_mode = "workspace-write"
notify = ["/Users/markz/.codex/computer-use/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]
service_tier = "priority"
model = "gpt-5.6-sol"
model_provider = "oneapi"
model_catalog_json = "/Users/markz/.codex/models-with-grok.json"
model_reasoning_effort = "xhigh"含义是:用户级默认模型是 gpt-5.6-sol,模型请求默认交给 oneapi provider,使用 xhigh 推理强度并请求 priority 服务通道;本地命令默认处在 workspace 可写、非全盘可写 的沙箱里。notify 是回合完成后的外部程序回调,和模型请求路径无关。
它不等于每次运行最终一定这样,因为 CLI 参数、profile、项目配置可能覆盖它。
| 字段 | 当前值 | 作用 | 边界 |
|---|---|---|---|
sandbox_mode | workspace-write | 本地命令的沙箱模式。Codex 可以在工作区范围内写文件,例如当前项目目录、允许的 workspace roots。 | 它不等于“全盘可写”。写工作区外的文件、联网、提权命令,仍可能被拒绝或需要审批。 |
service_tier | priority | 模型服务档位 / 服务通道请求。源码注释里常见值包括 default、priority、flex,旧版 fast 也兼容。 | 它影响 Codex 请求模型服务时偏向哪个服务通道,是否生效取决于账号、模型和 provider;它不是模型名,也不是权限开关。 |
model_reasoning_effort | xhigh | 推理强度。值越高,通常表示希望模型在复杂推理、代码理解、排查问题时投入更多 reasoning。 | 它只在模型 / provider 支持时才有意义;可能带来更慢响应或更高消耗,不改变请求发往哪个 provider。 |
model | gpt-5.6-sol | 默认模型名。Codex 默认会向当前 provider 请求这个模型。 | 它只回答“用哪个模型”,不回答“请求发到哪里”。请求出口要继续看 model_provider 和 [model_providers.*]。 |
model_provider | oneapi | 默认模型请求出口。选择对应的 [model_providers.oneapi]。 | profile、CLI 参数或会话设置仍可能覆盖它。 |
model_catalog_json | 本地 JSON 路径 | 模型目录来源。决定 /model 列表及能力声明。 | 不负责请求出口和鉴权。 |
notify | SkyComputerUseClient turn-ended | 外部完成回调。Codex 回合完成后执行该 argv,并追加事件 JSON。 | 它不是 TUI 桌面通知开关,也不能替代完整 Hook 生命周期。 |
二、最终配置从哪些层叠出来?
Codex 不是只读一个文件,而是把多层配置合并成最终配置。同一个 key 如果在多层都出现,优先级更高的层覆盖更低的层。
常见个人使用场景里,优先级可以这样记:
| 优先级 | 配置层 | 适合放什么 |
|---|---|---|
| 最高 | CLI flags / --config / -c | 本次临时覆盖,例如临时改模型、权限、MCP 开关 |
| 高 | 项目级 .codex/config.toml | 只对当前 trusted 项目生效的偏好 |
| 中 | --profile 指定的 $CODEX_HOME/<profile>.config.toml | 一组可切换场景,例如深度审查、中转模型、只读分析 |
| 中低 | 用户级 $CODEX_HOME/config.toml | 个人长期默认值;默认展开为 ~/.codex/config.toml |
| 低 | 系统级 /etc/codex/config.toml | 机器级或团队默认值 |
| 最低 | Codex 内置默认值 | 没有配置时才使用 |
⚠️ 企业 managed config / requirements 可能额外约束某些值,例如不允许
approval_policy = "never"或不允许 full access。这类约束不是个人 config 能强行覆盖的。本文主要按个人本地配置讲。
显式值、内置默认值与最终有效值
config.toml 只记录显式配置,不会把 Codex 的全部默认值展开写回文件。某个字段没有出现,可能表示“功能未配置”,也可能表示“使用内置默认值”;必须结合字段定义判断,不能统一理解成 false。
以 TUI 通知为例,Codex CLI 0.146.0-alpha.3.1 的源码把三个字段的默认值定义为:
[tui]
notifications = true
notification_method = "auto"
notification_condition = "unfocused"这段配置即使没有出现在文件里,默认行为仍然成立:TUI 通知开启,发送方式由终端自动选择,并且只在终端失去焦点时投递。这里的代码块是在说明等价的默认行为,不是说 Codex 会自动把这三行写进 config.toml。
因此排查配置时要区分三个问题:
- 显式值:某一配置层实际写了什么;
- 内置默认值:所有配置层都没写时,客户端采用什么;
- 最终有效值:把配置层按优先级合并后,仅为已定义默认值的缺失字段补值;其余字段仍保持未配置。
CLI 的 -c / --config 直接覆盖本次进程,适合验证最终行为:
codex -c 'tui.notifications=false'-c 后面的 key=value 按 TOML 解析。上面的 false 是布尔值;字符串值要保留 TOML 引号,例如 -c 'tui.notification_method="osc9"'。这种覆盖不会修改磁盘上的配置文件,进程结束后也不会保留。
三、每一层应该怎么用?
$CODEX_HOME/config.toml 适合放个人稳定默认值;在默认 home 下就是 ~/.codex/config.toml。比如常用模型、默认沙箱、MCP server、插件启用状态、通知与 Hook、TUI 状态栏、桌面端打开文件偏好、项目 trust 状态。
.codex/config.toml 适合放“这个项目里才需要”的配置。官方文档和源码都明确限制了项目配置的能力:项目配置只在 trusted 项目中加载,而且不能覆盖会重定向凭据、改变 provider 鉴权或执行本机通知/遥测的 key。
项目配置里会被忽略的典型 key 包括:
openai_base_url
chatgpt_base_url
model_provider
model_providers
notify
profile
profiles
otel这条限制很重要:不要把模型中转 provider 写进项目 .codex/config.toml。provider 和认证相关配置应该放在用户级配置或 profile 文件里。
--profile 是“场景覆盖层”。Codex 0.134.0 以后,官方写法是单独建文件:
$CODEX_HOME/deep-review.config.toml
$CODEX_HOME/relay.config.toml然后这样启动:
codex --profile relayprofile 文件里写顶层 key,不要写成 [profiles.relay]:
# $CODEX_HOME/relay.config.toml
model = "DeepSeek-V4-Pro"
model_provider = "aimami_relay_4b291ef3da"
model_reasoning_effort = "high"当前 /Users/markz/.codex/config.toml 里 AiMaMi 托管块还包含 [profiles.aimami_relay]。这块标了 DO NOT EDIT MANUALLY,应按 AiMaMi 管理块理解,不要当成新版 Codex 的手写 profile 模板。按当前官方文档和源码,CLI --profile xxx 应该对应 $CODEX_HOME/xxx.config.toml。
四、模型配置先看三个字段
模型请求路径只需要先看三个字段:model、model_provider、model_providers.<id>。
三者关系是:
model:模型名,例如gpt-5.6-sol、DeepSeek-V4-Pro。它只回答“想用哪个模型”。model_provider:provider id,例如openai、aimami_relay_4b291ef3da。它回答“把模型请求交给谁发”。[model_providers.<id>]:provider 连接方式。这里才定义base_url、wire_api、认证方式、headers、重试和超时。
如果顶层没有写model_provider,通常会走默认 provider。当前用户级配置已经显式选择:
model = "gpt-5.6-sol"
model_provider = "oneapi"所以不带其他覆盖启动时,请求会交给 [model_providers.oneapi]。排查“现在到底走哪个 provider”时仍不能只看这个文件的顶层,还要看本次启动有没有 --profile、-c model_provider=...,以及 App 内是否有会话级选择覆盖。
五、中转配置就是一个 provider 例子
你写这篇文章的真实背景是:看不懂中转配置。中转不要先当成一个新概念,它本质上就是自定义 provider。
一个最小 provider 大概长这样:
model = "gpt-5.6-sol"
model_provider = "proxy"
[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
env_key = "PROXY_API_KEY"这段配置的意思是:
- 顶层
model_provider = "proxy"选中下面的[model_providers.proxy]。 base_url指向 OpenAI-compatible API 根地址,通常以/v1结尾。wire_api = "responses"表示 Codex 按 Responses API 协议组织请求。env_key = "PROXY_API_KEY"表示真正的 token 从环境变量PROXY_API_KEY取,不直接写死在 TOML 里。
你的 AiMaMi 当前生成了两个 provider,其中 DeepSeek-V4-Pro 这段可以这样读:
[model_providers.aimami_relay_4b291ef3da]
name = "oneapi [ds4p]"
base_url = "http://127.0.0.1:25817/codex/by-provider/aimami_relay_4b291ef3da/v1"
wire_api = "responses"
supports_websockets = false
requires_openai_auth = false
[profiles.aimami_relay]
model_provider = "aimami_relay_4b291ef3da"
model = "DeepSeek-V4-Pro"这里最关键的是:请求先发到本机 127.0.0.1:25817,再由 AiMaMi relay 转发到它管理的后端模型。requires_openai_auth = false 表示这个 provider 不要求 Codex 用 OpenAI 登录态去鉴权。
这段里还有 api_key = "aimami-relay"。在本文验证的 Codex CLI 0.146.0-alpha.3.1 源码中,ModelProviderInfo 没有把 api_key 定义为通用官方字段;官方字段里更常见的是 env_key、experimental_bearer_token 和 [model_providers.<id>.auth]。所以 api_key 这一行更应该按 AiMaMi 托管字段或兼容占位理解,不要把它当成手写官方 provider 的推荐写法。
六、provider 常用字段怎么读?
常用 provider 字段可以按用途分成四类:
| 字段 | 作用 | 常见写法 |
|---|---|---|
name | 显示名,便于 UI 或日志识别 | name = "OpenAI-compatible proxy" |
base_url | 模型 API 根地址 | base_url = "https://proxy.example.com/v1" |
wire_api | Codex 和 provider 之间的协议 | 当前按新版本理解为 responses |
env_key | 从环境变量读取 API key | env_key = "PROXY_API_KEY" |
experimental_bearer_token | 直接写 bearer token | 不推荐,除非临时或程序化场景 |
[model_providers.<id>.auth] | 通过命令动态获取 bearer token | 企业凭据助手、临时 token |
http_headers | 静态附加 header | { "X-Client" = "codex" } |
env_http_headers | 从环境变量读取 header 值 | { "X-Token" = "TOKEN_ENV" } |
query_params | 给请求 URL 附加 query | 特定网关要求时使用 |
request_max_retries | 普通 HTTP 请求重试次数 | 网络不稳或网关偶发失败时调 |
stream_max_retries | 流式响应断开后的重连次数 | 长输出、流式代理不稳时调 |
stream_idle_timeout_ms | 流式响应空闲多久算断开 | 慢模型或中转慢时调大 |
requires_openai_auth | 是否需要 OpenAI 登录/API key 鉴权 | 自定义中转通常是 false |
supports_websockets | 是否支持 Responses WebSocket 传输 | 普通 HTTP 中转通常是 false |
如果 provider 需要命令动态取 token,官方写法是:
[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
[model_providers.proxy.auth]
command = "/usr/local/bin/fetch-codex-token"
args = ["--audience", "codex"]
timeout_ms = 5000
refresh_interval_ms = 300000这里不要再同时写 env_key 或 requires_openai_auth。官方文档明确说 command-backed auth 不应该和这些认证方式混用。
七、wire_api 和 /v1/chat/completions 怎么理解?
你截图里那句 wire_api = "chat" 的意思是:让 Codex 用 Chat Completions 协议去请求 provider,也就是请求形态接近 /v1/chat/completions。
但这个点必须按版本谨慎看:
- 官方 Codex Manual 的模型页写过:Codex 可以指向支持 Chat Completions 或 Responses API 的 provider,但 Chat Completions 支持已经 deprecated。
- 本文验证的 Codex CLI
0.146.0-alpha.3.1源码里,WireApi枚举只有Responses;如果配置wire_api = "chat",反序列化会报错,提示wire_api = "chat"不再支持。
所以这篇笔记按当前新版本给结论:不要把wire_api = "chat"当成现在的推荐配置。如果某个中转平台后端只支持/v1/chat/completions,更稳的做法是让中转平台自己把 Codex 的 Responses 请求转换成 Chat Completions 请求,而不是在 Codex 里强行写wire_api = "chat"。
你的当前 AiMaMi 配置正好是:
wire_api = "responses"所以它不是截图里说的 Chat Completions 模式。它更像是:Codex 按 Responses 协议请求本机 AiMaMi relay,relay 再决定怎么转给后端。
八、推理、服务档位和模型能力
模型字段之外,还有几个常见顶层字段:
| 字段 | 作用 | 你当前的值 |
|---|---|---|
model_reasoning_effort | 推理强度,常见值包括 low、medium、high、xhigh | xhigh |
model_verbosity | 回答详略,官方说明只对 Responses API provider 生效 | 未显式配置 |
service_tier | 服务档位,例如 fast / flex / priority 这类产品侧档位 | priority |
model_context_window | 手动声明上下文窗口 | 未显式配置 |
这里最容易误解的是:这些字段不是所有模型和所有 provider 都保证支持。特别是 model_verbosity,官方文档明确说 Chat Completions provider 会忽略它。也就是说,越偏“Codex 原生 Responses 能力”的 provider,越可能完整吃到这些控制项;越偏普通 OpenAI-compatible Chat 网关,越可能只能兼容一部分。
九、权限配置和模型配置分开看
权限配置回答的是“Codex 能不能执行命令、能写哪里、什么时候问你”,不是“模型请求发到哪里”。
常见旧式写法是:
sandbox_mode = "workspace-write"
approval_policy = "on-request"两者分工不同:
sandbox_mode控制文件系统和执行环境边界,例如read-only、workspace-write、danger-full-access。approval_policy控制什么时候停下来问你,例如untrusted、on-request、never。
你当前写了:
sandbox_mode = "workspace-write"这表示 Codex 默认可以在 workspace 范围内写文件,写 workspace 外、联网或提升权限时仍可能需要审批。你没有在顶层显式写 approval_policy,所以会走默认值或本次启动覆盖。
新版官方文档还提到 default_permissions 和 [permissions.*]。它们是把文件系统、网络、workspace roots 等权限组合成可复用 profile 的新方式。官方建议:如果你开始用 permission profiles,就不要在同一个会话里再混用旧的 sandbox_mode / sandbox_workspace_write 思路。
十、MCP、Plugin、Project trust 在 config 里各自是什么?
mcp_servers.* 是工具入口配置。它告诉 Codex 怎么启动或连接一个 MCP server。
本地 stdio MCP 常见写法:
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
env_vars = ["LOCAL_TOKEN"]
startup_timeout_sec = 20远程 HTTP MCP 常见写法:
[mcp_servers.datapilot]
url = "https://datapilot.baidu-int.com/mcp"你当前就有两类 MCP:
mcp_servers.node_repl:本地命令启动,带一批env。mcp_servers.datapilot:远程 HTTP 地址。
plugins.*是插件启用状态。你的配置里启用了figma、chrome、computer-use、browser、documents、spreadsheets、iplugin等插件。但插件启用不等于里面所有能力都一定可用,有些还要看产品授权、系统权限、MCP server 是否启动、外部服务是否登录。
projects."<path>".trust_level是项目 trust 状态。你的/Users/markz/Desktop/sql当前是:
[projects."/Users/markz/Desktop/sql"]
trust_level = "trusted"所以这个目录下的项目级 .codex/config.toml、项目 hooks、项目 rules 才会被 Codex 加载。未 trusted 的项目会跳过这些 project-local 层。
十一、本地交互配置:TUI 通知、notify 与 Hook
模型、权限和外部工具决定 Codex“怎么工作”,本地交互配置决定“什么时候提醒用户、什么时候调用外部程序、什么时候介入生命周期”。tui.notifications、顶层 notify 和 Hook 都可能在回合结束附近产生动作,但它们不是同一个机制。本节的 TUI 默认值特指 CLI 终端界面;App 和 IDE 如何呈现通知,由各自的 UI 实现决定。
TUI 通知
TUI 通知由 Codex 在终端界面中直接发出,不需要额外脚本。前文列出的三个默认值分别控制是否发送、用什么终端协议发送,以及终端处于什么焦点状态时发送:
| 字段 | 0.146.0-alpha.3.1 默认值 | 作用 |
|---|---|---|
tui.notifications | true | 开启全部 TUI 通知;也可以改成事件名数组 |
tui.notification_method | "auto" | 自动选择 OSC 9 或 BEL |
tui.notification_condition | "unfocused" | 只在终端失去焦点时投递 |
当前版本的通知事件分为三类:
| 事件名 | 触发场景 |
|---|---|
agent-turn-complete | Agent 完成一个回合 |
approval-requested | 命令、文件修改或 MCP 请求等待审批 |
plan-mode-prompt | Plan 模式等待用户选择 |
如果只关心其中一部分,可以把布尔值改成事件名数组:
[tui]
notifications = ["agent-turn-complete", "approval-requested"]
notification_method = "auto"
notification_condition = "unfocused"auto 会把 Ghostty、iTerm2、Kitty、Warp 和 WezTerm 识别为支持 OSC 9 的终端,其他终端回退到 BEL。OSC 9 通常表现为桌面通知;BEL 的实际效果取决于终端设置,可能是声音、标签闪烁或 Dock 提示。
这里还要区分“终端接收应用通知”和“终端监控任意命令完成”。Ghostty 默认允许终端内应用通过 OSC 9 / OSC 777 发桌面通知,但它自己的 notify-on-command-finish 默认是 never。因此 Codex 完成后出现 Ghostty 通知,通常是 Codex 主动发送 OSC 9,不代表 Ghostty 已经开启了通用命令完成提醒。
临时关闭 TUI 通知仍使用前文的 argv 覆盖:
codex -c 'tui.notifications=false'外部 notify 回调
顶层 notify 是一条外部程序 argv。数组第一个元素是 executable,后续元素是固定参数;Codex 不把它当成一段 shell 字符串解析。当前配置:
notify = ["/Users/markz/.codex/computer-use/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]Agent 回合完成后,Codex 会在这组 argv 末尾追加一个 JSON 参数。实际调用可以概括为:
SkyComputerUseClient turn-ended '{"type":"agent-turn-complete", ...}'事件 JSON 包含 thread、turn、cwd、输入消息和最后一条 Assistant 消息等信息。因此 notify 只应指向自己信任的本地程序;如果 wrapper 还会转发或记录 JSON,需要同时检查日志和外发边界。当前这个回调用于让 Computer Use 客户端感知回合结束;如果顶层没有配置 notify,该功能默认不执行。它和 TUI 通知彼此独立,所以关闭 tui.notifications 不会阻止 SkyComputerUseClient 运行。
排查通知来源时可以在本次进程里同时关闭外部回调:
codex -c 'notify=[]' -c 'tui.notifications=false'空 argv 不会启动外部程序;这仍然只是本次进程覆盖,不会删除原配置。需要把一个回合事件转发到多个目标时,应让 notify 指向一个稳定 wrapper,再由 wrapper 分发,不要把管道或多个命令直接塞进 argv 数组。
与 Hook 的职责边界
Codex 当前源码把顶层 notify 作为兼容回调接入 Hook 引擎,但它公开给用户的能力仍然很窄:只在 Agent 回合完成后被动启动一个外部程序。完整 Hook 则覆盖用户输入、工具执行、审批、上下文压缩、子 Agent 和结束等生命周期节点,并且部分事件能够阻断或改变后续流程。
| 机制 | 未显式配置时 | 触发范围 | 能否干预流程 |
|---|---|---|---|
| TUI 通知 | 默认开启 | 回合完成、审批、Plan 输入 | 不能,只负责提醒 |
顶层 notify | 默认不执行 | Agent 回合完成 | 不能,只启动外部程序 |
| Hook | 没有 handler 就不执行 | 多个生命周期事件 | 取决于事件,可观察、阻断或补充上下文 |
因此,“一个 Agent 回合完成时提醒我”优先使用 TUI 通知;“回合完成时通知另一个本地程序”使用 notify;“工具执行前拦截危险命令、结束前检查测试结果”才使用 Hook。这里的“回合完成”不等于整个 CLI 进程退出。Hook 的事件、阻断协议和安全边界详见 Agent Hook 完全指南。
十二、读你当前 config 的顺序
以后排查自己的配置,可以按这个顺序读,不要从头到尾逐行陷进去。
第一,看顶层默认值:
sandbox_mode = "workspace-write"
notify = [".../SkyComputerUseClient", "turn-ended"]
service_tier = "priority"
model = "gpt-5.6-sol"
model_provider = "oneapi"
model_reasoning_effort = "xhigh"这决定默认模型、请求出口、推理强度、服务档位、沙箱和外部完成回调。没有写出的字段还要继续查内置默认值。
第二,看本次运行有没有覆盖层:
codex --profile ...
codex -c model=...
codex -c model_provider=...这些比 $CODEX_HOME/config.toml 更优先。
第三,看模型请求是否走中转:
model_provider = "oneapi"当前默认继续看 [model_providers.oneapi] 的 base_url、wire_api 和认证字段;如果 profile 把 provider 切到 AiMaMi,再改看对应的 [model_providers.aimami_relay_*]。
第四,看 MCP:
[mcp_servers.node_repl]
[mcp_servers.datapilot]模型中转只影响模型请求,不会自动让 MCP、plugin、connector 获得授权。
第五,看 plugins、features、desktop、tui、notify 和 hooks。这里既有显式配置,也有 TUI 通知这类默认开启的行为;排查时要同时看配置文件、内置默认值和 argv 覆盖。
十三、常见配置模板
日常开发:workspace 可写,必要时问我
model = "gpt-5.6-sol"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
approval_policy = "on-request"
web_search = "cached"只读分析:尽量不改文件
model = "gpt-5.6-sol"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
approval_policy = "untrusted"中转模型:Responses 优先
model = "DeepSeek-V4-Pro"
model_provider = "proxy"
[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
env_key = "PROXY_API_KEY"
requires_openai_auth = false
supports_websockets = falseProfile 文件:把中转做成可切换场景
# $CODEX_HOME/relay.config.toml
model = "DeepSeek-V4-Pro"
model_provider = "proxy"
[model_providers.proxy]
name = "OpenAI-compatible proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
env_key = "PROXY_API_KEY"启动时:
codex --profile relay十四、常见误区
误区一:改了 model 就等于改了请求出口。
不是。model 只是模型名,请求出口由 model_provider 和 [model_providers.<id>] 决定。
误区二:项目 .codex/config.toml 可以配置中转。
不应该。项目级配置不能覆盖 model_provider、model_providers、openai_base_url 这类会重定向凭据或 provider 的 key。
误区三:wire_api = "chat" 是现在的通用解法。
不是。旧资料里它表示 Chat Completions 协议,但当前 Codex 源码已经只接受 responses。如果中转平台只支持 Chat,应该让中转层做协议转换,或者确认你使用的是仍支持 chat 的旧 Codex 版本。
误区四:插件启用了,工具就一定能用。
不一定。plugin 只是分发和启用入口;MCP 是否启动、App/Connector 是否授权、系统权限是否打开、workspace policy 是否允许,都会影响最终可用性。
误区五:web_search = "live" 等于 shell 命令可以联网。
不是。web_search 控制 Codex 的第一方 web search 工具;shell 里的 curl、npm install、git fetch 是否能联网,仍受 sandbox、network permission 和审批策略影响。
误区六:改了 model_catalog_json 指向的模型目录,就等于已经切到了某个自定义模型。
不是。模型目录只决定菜单和能力声明;当前默认模型看 model,请求出口看 model_provider。
误区七:config.toml 没写某个布尔字段,就等于它是关闭的。
不一定。字段缺失后可能使用内置默认值;例如 tui.notifications 默认就是 true。要判断最终行为,必须同时检查配置层、字段默认值和本次 argv 覆盖。
十五、专题案例:model_catalog_json 与模型目录
到这里,常用配置的读法和本地通知边界已经完整。下面保留一个更深的模型目录排查案例;没有配置 model_catalog_json 时可以直接跳到参考链接。
前面讲的 model / model_provider 只回答“默认用哪个模型、请求发给谁”。model_catalog_json 还会决定 /model 菜单里会出现哪些模型、每个模型支持哪些 reasoning 档位、缺字段时会不会直接启动失败。
1. 它是不是 model 读取的?
是,但只负责“模型元数据目录”,不负责真正发请求。
先看这张图,再看下面的表:
可以把它拆成三层看:
| 层 | 配置 | 回答的问题 | 不回答的问题 |
|---|---|---|---|
| 模型目录 | model_catalog_json = "/path/to/models.json" | /model 列表有哪些项、显示名、默认 reasoning、是否显示 | 请求发到哪里、怎么鉴权 |
| 当前选中模型 | model = "my-model" | 这次会话默认选哪个 slug | 这个 slug 有没有出现在目录里、能不能解析目录 |
| 请求出口 | model_provider + [model_providers.*] | 请求发到哪个 base_url、用什么凭据 | 菜单里显示哪些模型 |
一句话:
- 模型目录决定 菜单和模型能力声明。
model决定 当前默认选中哪个。model_provider决定 真正请求打到哪里。
最小接线只需要在 config.toml 里加一行:
# $CODEX_HOME/config.toml
model_catalog_json = "/absolute/path/to/models-catalog.json"多个客户端如果都指向同一份目录,菜单项会一致;各自的默认 model 仍可不同。
2. 启动时怎么读?
启动时可以先按下面这条链路理解:
文字版链路:
读取 config.toml
-> 看到 model_catalog_json
-> 解析 JSON
-> 校验每个 model 是否包含必填字段
-> 过滤 visibility == "list" 的项给 /model 菜单
-> 用 model / profile / CLI -m 决定当前选中项
-> 真正发请求时再走 model_provider如果 JSON 解析或 schema 校验失败,Codex 会在启动阶段直接报错,连 TUI 都进不去。典型错误类似:
Error loading configuration: failed to parse model_catalog_json path
`/absolute/path/to/models-catalog.json` as JSON:
missing field `supports_reasoning_summaries` at line N column M注意:这里写 “as JSON”,但文件本身可以是合法 JSON;失败点常常是 字段 schema 不完整,不是花括号写坏了。
3. supports_reasoning_summaries 是什么?
它是模型目录里的一个 布尔能力字段,表示客户端是否按“支持 reasoning summary”处理这个模型。
和它相邻、容易一起混的字段有:
| 字段 | 类型 | 作用 |
|---|---|---|
supports_reasoning_summaries | bool | 客户端是否按“支持 reasoning summary”处理这个模型 |
default_reasoning_summary | string | 默认 summary 策略,常见如 "none" |
supported_reasoning_levels | array | 这个模型允许切换哪些 reasoning effort |
default_reasoning_level | string | 切到这个模型时默认 effort |
model_reasoning_effort(在 config.toml) | string | 用户/会话层当前选中的 effort,覆盖默认值 |
关键边界:
- 它 不是 模型名,也 不是 provider。
- 它 不是 “开了就一定有 reasoning 文本给你看”;还受
default_reasoning_summary、客户端 UI、provider 实际能力影响。 - 在当前 CLI 里它更像 schema 必填能力声明。缺了就解析失败;补上合法布尔值后,客户端才能完成目录加载。
一个常见修复就是给目录里每个模型补上:
"supports_reasoning_summaries": true这只影响模型目录能否被解析,不自动改默认模型,也不改 provider。
4. 模型目录怎么读?
文件顶层通常很简单,只有一个数组:
{
"models": [
{ "...一个模型..." },
{ "...另一个模型..." }
]
}visibility 决定是否进菜单:
list:出现在/modelhide:目录里保留,但不默认展示
下面用一个自定义模型条目举例。注意:真实 JSON 文件不能写注释;这里用 JSONC 只为讲解字段。
// 真实 models-catalog.json 不能写注释。
// 真正发请求不看这份目录,而看 config.toml 里的 model_provider。
{
// 模型 ID。必须和 config.toml / profile 里的 model = "..." 完全一致
"slug": "my-model",
// /model 菜单上显示的名字
"display_name": "My Model",
// 菜单项说明文案
"description": "A custom model exposed through the configured relay.",
// list = 出现在 /model;hide = 目录里有但不展示
"visibility": "list",
// 菜单排序权重,数字越小通常越靠前
"priority": 4,
// 切到这个模型时的默认 reasoning 档位
"default_reasoning_level": "high",
// 这个模型允许切换的 reasoning 档位列表
"supported_reasoning_levels": [
{ "effort": "low", "description": "Fast responses with lighter reasoning" },
{ "effort": "medium", "description": "Balances speed and reasoning depth for everyday tasks" },
{ "effort": "high", "description": "Greater reasoning depth for complex problems" },
{ "effort": "xhigh", "description": "Extra high reasoning depth for complex problems" }
],
// 是否按“支持 reasoning summary”处理;当前 CLI 里接近 schema 必填字段
// 缺了可能启动失败:missing field `supports_reasoning_summaries`
"supports_reasoning_summaries": true,
// 默认 summary 策略。none = 默认不强调输出 reasoning summary
"default_reasoning_summary": "none",
// 是否支持 verbosity(回答详略)相关控制
"support_verbosity": true,
// 默认回答详略
"default_verbosity": "low",
// 本地 shell 工具形态。shell_command = 走命令执行工具
"shell_type": "shell_command",
// 声明的上下文窗口大小(token)
"context_window": 272000,
// 允许的最大上下文窗口
"max_context_window": 272000,
// 是否倾向走 responses lite 路径;false 表示按完整 responses 能力处理
"use_responses_lite": false
}字段可以按用途分组读:
| 分组 | 字段 | 你怎么理解 |
|---|---|---|
| 菜单展示 | slug、display_name、description、visibility、priority | /model 显示什么、排第几、是否隐藏 |
| Reasoning 能力 | supported_reasoning_levels、default_reasoning_level、supports_reasoning_summaries、default_reasoning_summary | 能切哪些 effort、默认多少、summary 怎么处理 |
| 输出与工具形态 | support_verbosity、default_verbosity、shell_type、apply_patch_tool_type、web_search_tool_type | 回答详略、命令壳类型、补丁/搜索工具形态 |
| 上下文 | context_window、max_context_window、effective_context_window_percent | 上下文窗口声明 |
| 系统提示 | base_instructions、model_messages | 这个模型在客户端侧的基础指令模板;通常很长,不建议手改 |
| 协议偏好 | use_responses_lite、supports_search_tool 等 | 客户端倾向怎么调用模型能力 |
slug 必须和你在 config.toml / profile 里写的 model 对得上。例如:
model = "my-model"这里的 my-model 就是目录里的 slug,不是 display_name。
5. 目录有了,不等于请求一定能通
这是最容易混的一点。
目录只保证:
- Codex 能启动并解析模型列表。
/model里能看到对应菜单项。- 客户端知道它支持哪些 reasoning 档位和部分能力开关。
真正请求还要继续看 provider。很多自定义模型并不是 Codex 直连上游,而是先打到本地/自建 relay,再由 relay 转发。一个通用例子:
model_provider = "relay"
[model_providers.relay]
name = "Local relay"
base_url = "http://127.0.0.1:3000/v1"
env_key = "RELAY_API_KEY"
wire_api = "responses"
requires_openai_auth = false如果只是偶尔切到某个自定义模型,也可以做成 profile,而不是改默认:
# $CODEX_HOME/my-model.config.toml
model_provider = "relay"
model = "my-model"启动:
codex --profile my-model于是:
- 菜单项来自模型目录
- 当前模型变成
my-model - 请求出口变成本地
relay
如果只改目录、不配 provider,结果通常是“菜单里有这项,但一发请求就找不到正确出口或鉴权失败”。
6. 只加选项、不改默认,应该怎么配
目标如果是“默认模型不变,只多一个可选项”,正确组合是:
# $CODEX_HOME/config.toml
model = "gpt-5.6-sol" # 保持你原来的默认
model_catalog_json = "/absolute/path/to/models-catalog.json"
[model_providers.relay]
name = "Local relay"
base_url = "http://127.0.0.1:3000/v1"
env_key = "RELAY_API_KEY"
wire_api = "responses"
requires_openai_auth = false需要切到自定义模型时:
- TUI 里
/model选择对应项 - 或
codex --profile my-model
不要把“加一个选项”理解成必须执行“把默认 model 永久改成自定义模型”。那会改默认,不是“只加选项”。
token / API key 应放在环境变量或系统钥匙串里,不要写进模型目录,也不要直接写进可分享的笔记正文。
7. 排查顺序
以后再遇到“模型菜单/自定义模型”问题,按这个顺序看:
config.toml有没有model_catalog_json- 路径文件在不在、是不是合法 JSON
- 每个 model 是否缺必填字段,尤其是新版本新增字段如
supports_reasoning_summaries - 目标模型
visibility是不是list model/ profile 里的 slug 是否等于目录里的slug- 当前
model_provider和[model_providers.*]是否指向可用 provider env_key对应环境变量有没有值
前 4 步挂了,常见表现是启动失败或菜单没有该项;后 3 步挂了,常见表现是菜单有项但请求失败。
十六、参考链接
- OpenAI Codex Config basics
- OpenAI Codex Advanced Config
- OpenAI Codex Configuration Reference
- OpenAI Codex Models
- OpenAI Codex MCP
- OpenAI Codex Permissions
- OpenAI Codex 0.146.0-alpha.3.1:TUI 通知默认值
- OpenAI Codex 0.146.0-alpha.3.1:终端通知后端选择
- OpenAI Codex 0.146.0-alpha.3.1:外部 notify 回调
- Ghostty Configuration Reference:Command finished notifications
- OpenAI Codex 0.146.0-alpha.3.1:config loader
- OpenAI Codex 0.146.0-alpha.3.1:model provider info