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

AI工具56 次阅读9 分钟
image.png

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 的模式切换背后牵涉三个不同状态层。

Fast 模式成本与速度对比

问题现象

~/.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 稳定有的模块:jsonrepathlibshutil

Codex Fast 状态写回链路

验证

/usr/bin/python3 - <<'PY'
from pathlib import Path
import json, re, datetime

home = 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。不能只看磁盘文件。


复发排查顺序

  1. 检查 config.tomlfast_default_opt_out = true + fast_mode = false + 无顶层 service_tier = "fast"

  2. 检查 .codex-global-state.jsondefault-service-tier = null + has-user-changed-service-tier = true

  3. 看 watcher log:tail -100 ~/.codex/tmp/disable-fast-after-codex-exit-launchd.log

  4. watcher 没成功:检查是否被 launchctl remove、是否依赖了系统 Python 没有的模块、是否 patch 太早被 Electron flush 覆盖

  5. 以上都对但仍然 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、退出时写回,三件事缺一个,它就会悄悄回来。