Codex Desktop For Intel Mac 总是默认 Fast 模式:根因和修复方法

Codex Desktop For Intel Mac 在
~/.codex/config.toml 写了 fast_default_opt_out = true,重启后 UI 还是 Fast。
这不是配置写错了,是三层状态同时在打架。
为什么要关
Fast 模式大约带来 1.5x 速度提升,但 token 消耗放大到约 2.5x。对重量级模型来说这个差值不小。如果只是偶发使用无所谓,但把 Codex 当日常工程工具的话,账单会一直比预期高。
更麻烦的是 Intel Mac 上没有稳定的关闭入口,UI 的模式切换背后牵涉三个不同状态层。

问题现象
~/.codex/config.toml 已经写了:
[notice]
fast_default_opt_out = true但 ~/.codex/.codex-global-state.json 里还是:
{ "default-service-tier": "fast" }只检查 config.toml 会误判。Desktop UI 拿的不是这个文件。
根因:三层状态叠加
Layer 1 — 账号计划的 managed default
Codex Desktop 26.422.30944 对部分 ChatGPT business / enterprise 账号有 managed Fast default 逻辑(PR #19053)。官方反馈 issue #19230 已关闭,标 not planned。
Layer 2 — Desktop UI persisted state
Desktop 在 ~/.codex/.codex-global-state.json 里保存 UI 级别的状态,独立于 config.toml。只要 default-service-tier 是 "fast",config 文件写什么都没用。
Layer 3 — 退出时写回 + watcher crash
用 launchd 托管的退出后 patch watcher 一直 crash:
ModuleNotFoundError: No module named 'tomllib'launchd 调用的是系统 /usr/bin/python3,没有 tomllib。watcher 启动即挂,没有完成任何 patch。
实际链路:
config.toml 已 opt-out
→ App 运行时内存态仍是 fast
→ 退出时写回 .codex-global-state.json
→ watcher crash,没完成补丁
→ 下次打开继续 Fast修复:三件事同时处理
1. 固定 durable config
~/.codex/config.toml:
[notice]
fast_default_opt_out = true[features]
fast_mode = false
fast_default_opt_out 只是 opt-out 信号,fast_mode = false 才是功能开关,两个都要写。确认顶层没有 service_tier = "fast"。
2. 修正 Desktop persisted state
修改 ~/.codex/.codex-global-state.json 和 .bak,把 electron-persisted-atom-state 里改成:
{
"default-service-tier": null,
"has-user-changed-service-tier": true
}null 回到 Standard;has-user-changed-service-tier: true 告诉 Desktop 用户已明确选过,不再套用 enterprise_default。
3. 退出后再 patch 一次
Codex App 退出时有最后一次 state flush,会把改好的状态写回 fast。需要用 launchd submit 托管一个独立 watcher,在 App 完整退出 2 秒后再做 patch。
关键:不依赖 tomllib,只用系统 Python 稳定有的模块:json、re、pathlib、shutil。

验证
/usr/bin/python3 - <<'PY'
from pathlib import Path
import json, re, datetimehome = Path.home()
config = home / '.codex/config.toml'
text = config.read_text()
print('config_has_service_tier', bool(re.search(r'^\sservice_tier\s=', text, re.M)))
print('config_fast_default_opt_out_true', bool(re.search(r'^\sfast_default_opt_out\s=\strue\s$', text, re.M)))
print('config_fast_mode_false', bool(re.search(r'^\sfast_mode\s=\sfalse\s$', text, re.M)))
for name in ['.codex-global-state.json', '.codex-global-state.json.bak']:
p = home / '.codex' / name
atom = json.loads(p.read_text()).get('electron-persisted-atom-state', {})
print(name, {
'default-service-tier': atom.get('default-service-tier'),
'has-user-changed-service-tier': atom.get('has-user-changed-service-tier'),
})
PY
期望输出:
config_has_service_tier False
config_fast_default_opt_out_true True
config_fast_mode_false True
.codex-global-state.json {'default-service-tier': None, 'has-user-changed-service-tier': True}真正的验收是:完整退出 → 等 watcher 执行 → 重开 → UI 不再默认 Fast。不能只看磁盘文件。
复发排查顺序
检查
config.toml:fast_default_opt_out = true+fast_mode = false+ 无顶层service_tier = "fast"检查
.codex-global-state.json:default-service-tier = null+has-user-changed-service-tier = true看 watcher log:
tail -100 ~/.codex/tmp/disable-fast-after-codex-exit-launchd.logwatcher 没成功:检查是否被 launchctl remove、是否依赖了系统 Python 没有的模块、是否 patch 太早被 Electron flush 覆盖
以上都对但仍然 Fast:查
~/Library/Application Support/Codex/Local Storage/leveldb/
结论
config.toml 是 durable config,但不是 Desktop UI 的唯一状态源。.codex-global-state.json 是 Desktop 自己持久化的 UI 状态,退出时会被写回。退出后修复必须独立于 App 生命周期,launchd 环境很干净,不要假设它有当前 shell 的 Python 或 PATH。
Fast 模式不是改一个配置项就能关掉的。durable config、Desktop persisted state、退出时写回,三件事缺一个,它就会悄悄回来。