1. Codex 日志刷盘问题到底有多严重
如果你最近在用 Codex 做日常编码,可能会发现一个很隐蔽的问题:硬盘灯几乎没停过,~/.codex/logs_2.sqlite这个文件体积涨得飞快。我实测下来,连续跑几天之后,SSD 的累计写入量会明显高于正常开发场景,这不是错觉,而是日志模块的默认配置在“疯狂刷盘”。
先说清楚logs_2.sqlite是什么。Codex 在运行过程中会把会话、工具调用、底层通信细节写进一个本地 SQLite 数据库,路径通常在用户目录下的.codex文件夹里。它的作用是方便回溯和调试,但问题在于默认日志级别开到了 TRACE。TRACE 是最啰嗦的一档,会把 WebSocket 原始数据包、文件读写、内部状态变化全部记下来,其中大量内容对普通使用者没有价值。
写入放大是另一个关键点。SQLite 每次插入、删除都会触发页级操作,小块高频写入会让 SSD 实际物理写入量远超逻辑数据量。有用户反馈连续运行 21 天写入约 37TB,按这个速度一年就是几百 TB,而主流 1TB 消费级 SSD 的 TBW 通常在 600TB 左右。也就是说,放任不管,一块新盘可能在一年内被写废。
更麻烦的是,Codex 对标准的RUST_LOG环境变量并不完全买账,你没法用常规方式把日志级别调低。所以这篇教程的思路是两条腿走路:一是从 SQLite 层面直接拦截日志表的插入,二是从日志级别和运行方式上减少刷盘频率。下面我会给出可复制的 PRAGMA 配置、触发器写法,以及用系统工具验证写入下降的对比方法。适合所有在本地跑 Codex、关心 SSD 寿命的开发者。
2. 动手前的准备:TaoToken 与 Codex 环境确认
在改配置之前,先把运行环境理清楚。Codex 本身是一个编码 Agent 工具,它需要模型服务来驱动。如果你还在为模型调用发愁,可以用 TaoToken 这类兼容 OpenAI 接口的服务来承接请求,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。它的作用是提供稳定的模型对话与编码能力,让你在本地跑 Codex 时不用折腾账号和网络。
确认 Codex 安装位置。不同系统下.codex目录不一样:
- Windows:
%USERPROFILE%\.codex\logs_2.sqlite - macOS / Linux:
~/.codex/logs_2.sqlite
先确认这个文件存在,并且知道它当前多大。Windows PowerShell 里执行:
Get-Item "$env:USERPROFILE\.codex\logs_2.sqlite" | Select-Object FullName, LengthmacOS / Linux 终端里执行:
ls -lh ~/.codex/logs_2.sqlite记下当前大小,后面验证时会用到。接着确认sqlite3命令行工具是否可用:
sqlite3 --version如果显示版本号,比如3.39.5,就可以直接进入下一步。如果提示command not found或'sqlite3' 不是内部或外部命令,需要先安装。macOS 用 Homebrew:brew install sqlite3;Debian/Ubuntu 用sudo apt install sqlite3;Windows 去 SQLite 官网下载sqlite3.exe,解压后放到C:\Windows目录,再开一个新的 CMD 窗口验证。
这里有个容易忽略的点:操作数据库前一定要先关闭 Codex。因为 SQLite 在进程持有连接时会加锁,你直接改表结构或建触发器可能报database is locked。关闭 Codex 后再执行下面的命令,触发器会立即生效,不需要重启电脑。
另外,如果你用的是 Cline、Claude Code 这类工具配合 Codex,建议把三件套记牢:Base URL 填https://taotoken.net/api,Key 在控制台生成,Model ID 按你订阅的模型填。这样排查问题时能快速定位是模型侧还是本地日志侧的问题。
3. 可复制的 PRAGMA 配置与日志拦截方案
这一节是核心操作。目标有两个:降低 SQLite 的刷盘频率,以及直接阻止日志表被高频插入。先给出一份可复制的配置片段,你可以按系统选择。
3.1 SQLite PRAGMA 调优配置
SQLite 默认的synchronous=FULL会在每次事务提交时强制 fsync,对 SSD 写入压力很大。把日志库改成synchronous=NORMAL或OFF,并调整 journal 模式,可以显著减少物理写入。下面是一份 TOML 风格的配置参考,你可以把它写进自己的启动脚本或初始化逻辑里:
# codex-sqlite-tuning.toml [sqlite] journal_mode = "WAL" # 预写日志,减少随机写 synchronous = "NORMAL" # 降低 fsync 频率 temp_store = "MEMORY" # 临时表放内存 cache_size = -8000 # 约 8MB 页缓存 mmap_size = 268435456 # 256MB 内存映射 wal_autocheckpoint = 2000 # 每 2000 页做一次 checkpoint如果你更习惯用 JSON 描述,也可以写成:
{ "sqlite": { "journal_mode": "WAL", "synchronous": "NORMAL", "temp_store": "MEMORY", "cache_size": -8000, "mmap_size": 268435456, "wal_autocheckpoint": 2000 } }这些参数的含义:WAL 模式让读写分离,写入先落日志再合并;synchronous=NORMAL在 WAL 下已经足够安全,同时大幅减少 fsync;cache_size为负值表示 KB,-8000就是 8MB;mmap_size用内存映射减少 read 系统调用。实测下来,这一组配置能把日志库的写入频率压下来一大截。
3.2 用触发器直接拦截日志插入
如果只想快速止血,最直接的办法是在logs表上建一个 BEFORE INSERT 触发器,让所有插入被忽略。先关闭 Codex,然后执行对应系统的命令。
Windows PowerShell:
sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite" "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"macOS / Linux:
sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"执行后不会有输出,这是正常的。触发器立即生效,之后 Codex 再往logs表写数据会被直接忽略,不再产生新的磁盘写入。想确认触发器是否建好,可以查询:
sqlite3 ~/.codex/logs_2.sqlite "SELECT name FROM sqlite_master WHERE type='trigger';"应该能看到block_log_inserts。如果以后想恢复日志,删除触发器即可:
sqlite3 ~/.codex/logs_2.sqlite "DROP TRIGGER IF EXISTS block_log_inserts;"3.3 日志级别与启动方式调整
除了数据库层,还可以从日志级别入手。Codex 对RUST_LOG不完全响应,但你可以尝试在启动前设置:
export RUST_LOG=warnWindows PowerShell:
$env:RUST_LOG="warn"如果无效,就依赖上面的触发器方案。另外,把 Codex 的日志目录软链接到内存盘(如 Linux 的/dev/shm)也是一种思路,但重启会丢数据,适合临时调试。长期方案还是触发器加 PRAGMA 调优。
4. 验证请求与写入频率下降的对比方法
改完配置不能只看感觉,要用数据说话。这一节给出可复制的验证步骤,对比操作前后的写入量。
4.1 记录操作前的基线
先关闭 Codex,记录logs_2.sqlite的当前大小和系统累计写入。Linux 下可以用/proc/diskstats或iostat:
iostat -dx 5 3关注kB_wrtn/s这一列。macOS 可以用iostat -d disk0 5。Windows 用性能监视器看PhysicalDisk\Disk Writes/sec。
同时记录文件大小:
stat -c "%s %n" ~/.codex/logs_2.sqlite4.2 启动 Codex 跑一段固定任务
让 Codex 执行一段相同的编码任务,比如生成一个模块或跑一次对话,持续 5 分钟。期间用iostat采样,记录平均写入速率。
4.3 对比触发器生效后的数据
建好触发器后再跑同样的任务,重复采样。正常情况下,logs_2.sqlite的大小几乎不再增长,kB_wrtn/s会明显下降。下面是一个对照表模板,你可以填自己的实测值:
| 指标 | 操作前 | 操作后 |
|---|---|---|
| logs_2.sqlite 大小 | 持续增长 | 基本不变 |
| 平均写入速率 | 高 | 显著降低 |
| 触发器状态 | 无 | block_log_inserts |
4.4 用 SQLite 自身统计确认
还可以查询页数和空闲页,判断写入是否被拦截:
sqlite3 ~/.codex/logs_2.sqlite "PRAGMA page_count; PRAGMA freelist_count;"如果触发器生效,page_count在多次运行后应保持稳定。这一步能排除“文件没涨但 WAL 在涨”的假象,记得同时看-wal文件大小。
5. 本篇常见报错排查
操作过程中容易遇到几个典型报错,这里逐个说明。
database is locked:说明 Codex 还在运行,SQLite 连接被占用。解决方法是完全退出 Codex,包括后台进程。Windows 可以在任务管理器里结束相关进程,macOS/Linux 用pkill -f codex。
no such table: logs:说明数据库结构和你预期不一致,可能版本不同。先列出所有表:
sqlite3 ~/.codex/logs_2.sqlite ".tables"如果表名不是logs,把触发器语句里的表名换成实际名称。
401 或 local proxy failed:这类报错通常出现在模型请求侧,不是日志问题。检查你的 Base URL 是否为https://taotoken.net/api,Key 是否有效,Model ID 是否填对。三件套缺一不可。如果用的是 Claude Code 或 Cline MCP,确认配置文件里的地址和密钥没有多余空格。
reading choices 报错:一般是响应格式不匹配,检查模型 ID 是否支持当前接口。可以在模型对话页面先单独验证一次请求,确认服务正常后再回到 Codex。
OAuth 相关报错:如果你用的是需要 OAuth 的客户端,确认授权是否过期。Codex 的auth.json里保存了凭证,路径通常在~/.codex/auth.json。检查里面的字段是否完整,必要时重新登录生成。
触发器建了但文件还在涨:可能是 WAL 文件在增长,或者有其他表在写入。用.tables看全部表,对高频写入的表都加触发器,或者直接对数据库设置PRAGMA journal_mode=DELETE临时观察。
sqlite3 命令找不到:回到第 2 节安装步骤,Windows 注意把sqlite3.exe放到 PATH 包含的目录,重开终端再试。
6. 长期编码与 Agent 场景的稳定接入
把日志问题压下去之后,Codex 才能长期稳定地跑在本地。对于需要长时间编码、跑 Agent 任务的场景,建议把模型接入也固定下来。TaoToken 的 Coding Plan 适合这种持续调用的需求,地址是 https://taotoken.net/api ,配合控制台生成的 Key 使用。控制台在 https://taotoken.net/console ,API Keys 管理在 https://taotoken.net/api-keys ,接入文档在 https://taotoken.net/doc ,模型对话验证在 https://taotoken.net/chat 。Claude Code 相关配置参考 https://taotoken.net/claude-code 。
实际使用时,把 Base URL、Key、Model ID 三件套写进对应客户端的配置文件。比如 Cline 的 MCP 配置、Codex 的auth.json、Claude Code 的 settings,都要保证地址一致。这样即使日志侧做了拦截,模型请求依然正常,不会因为排查日志而影响编码效率。
最后提醒一句:触发器方案是应急止血,等官方修复补丁发布后,建议删掉触发器恢复日志功能,同时保留 PRAGMA 调优参数,让 SQLite 在正常范围内工作。SSD 寿命是消耗品,但没必要浪费在无用的 TRACE 日志上。