Codex CLI 疯狂写盘自救指南

Codex CLI 疯狂写盘自救指南

最近 OpenAI Codex CLI 被曝出一个相当严重的磁盘写入 bug:SQLite 日志在 TRACE 级别全量落盘,一年能写穿一块消费级 NVMe SSD。本文带你理解问题原理、检查自己是否中招,并提供从升级到触发器的完整自救方案。

这个 bug 到底在干什么

Codex CLI 会把运行日志写进一个本地 SQLite 数据库 ~/.codex/logs_2.sqlite。问题出在它在 TRACE 级别全量落盘——每一个 WebSocket 事件、每一条依赖库的噪声日志都往里写,而且不停地 insert + prune,造成严重的写放大。

社区实测(issue #28224):

  • 一台机器连续跑 21 天,向磁盘写了约 37 TB,折算下来一年约 640 TB
  • 消费级 NVMe SSD 的标称写入寿命(TBW)通常只有 600 TB 左右——理论上不到一年就能把一块盘的寿命写穿
  • 还带两个连锁故障:logs_2.sqlite 超过约 200MB 时 CLI 会以 SIGTRAP 崩溃(#29237);logs_2.sqlite-wal 能无上限涨到几十 GB(#28997)。

0.142.0(2026-06-22 发布)带来了部分修复:两个 PR——#29432(停掉逐条 WebSocket 事件日志)和 #29457(过滤噪声 target)——按 issue 估算能砍掉约 85% 的日志写入,纯命令行场景改善明显。

但它没有完全修好。 实测在 macOS + Codex 桌面 App(app-server) 路径下,即使内嵌 CLI 已经是 0.142.0,TRACE target=log 的高频写入依然存在——60 秒采样里 max(id) 还在前进 1400~1700 行,多人复现(#29532,OPEN)。所以本篇不只教你升级,还给你可靠检测 + 清理 + 在线止血的整套兜底。

还有一点:升级只能止住未来的写入,已经被写爆的旧日志不会自动回收。完整处置流程是 升级 → 真正确认停没停 → 没停就清理 / trigger 止血

一、确认自己中没中招

先看日志文件有多大,重点盯 -wal 那个:

1
2
ls -lah ~/.codex/logs_2.sqlite*
du -sh ~/.codex/

如果 logs_2.sqlite-wal 已经是几百 MB 甚至 GB 级,基本可以确定你正被这个 bug 持续写盘。

光看文件大小会骗你。 insert + prune 的 churn 会让 -wal 稳定在几 MB、logs_2.sqlite 体积也不怎么涨,看着没事,底层却一直在写(#29532 实测)。更靠谱的判定是隔 60 秒采两次行号,看它还涨不涨:

1
2
sqlite3 ~/.codex/logs_2.sqlite "SELECT MAX(id) FROM logs;"
# 等 60 秒再跑一次;数值仍在大幅前进(几百上千)= 正在持续写盘

Windows 路径是 %USERPROFILE%\.codex\,直接在资源管理器里看这三个 logs_2.sqlite* 文件的大小即可。

二、升级到 0.142.0

两种方式任选其一。

方式一:npm 手动升级

1
npm install -g @openai/codex@latest

@latest 当前解析到的就是 0.142.0,已经包含 #29432 / #29457 这两个修复。

升级完确认版本:

1
codex --version

方式二:让 AI 代办

如果你装了 Claude Code,把这句话丢给它,让它联网搜索并完成升级:

1
search web & update codex cli

必须是 0.142.0 或更高才带修复。0.141.x 及更早版本仍会疯狂写盘——重装同一个旧版本没用,认准版本号。升级后务必回第一步用 max(id) 采样复测,因为 macOS App 路径上它可能还没停(见下文)。

三、清理已经被写爆的旧日志

动手前先完全退出 Codex(命令行会话和桌面 App 都关掉)。文件还被占用时删不干净,WAL 也可能立刻重建。

这个日志库不含任何对话内容或配置,纯粹是诊断日志,删掉是安全的——Codex 下次启动会自动重建一个干净的小文件。

macOS / Linux:

1
rm ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite-wal ~/.codex/logs_2.sqlite-shm

或者一行通配搞定:

1
rm ~/.codex/logs_2.sqlite*

Windows(PowerShell):

1
Remove-Item "$env:USERPROFILE\.codex\logs_2.sqlite*"

四、验证修复生效

  1. 版本号到位:
1
2
codex --version
# 期望输出 0.142.0 或更高
  1. 别只看文件大小,按第一步那招采样 max(id),确认写入真的停了:
1
2
sqlite3 ~/.codex/logs_2.sqlite "SELECT MAX(id) FROM logs;"
# 隔 60 秒两次,数值基本不动 = 写入真停了

3.(进阶,Linux)直接看 SSD 实际写入量。用 smartctl 对比挂机前后的 Data Units Written

1
sudo smartctl -a /dev/nvme0 | grep "Data Units Written"

挂机一小时前后做差值,正常情况下增量应该很小,而不是之前那种几 GB/小时的量级。

如果你用的是 macOS + Codex 桌面 App,升到 0.142.0 后 max(id) 很可能仍在涨——这是已知的部分修复缺口(#29532)。这时别干等,直接上「进阶」里的 trigger 在线止血。

五、进阶:用 SQLite Trigger 在线止血

如果你不想删库、又想立刻止住写盘(尤其是升级到 0.142.0 后、在 macOS App 上仍没停的情况),可以用 SQLite trigger 拦截 logs 表的 insert。日志全落在一张名为 logs 的表里(主键 id),所以能精确处理。这也正是 0.142.0 之后社区在 macOS App 上仍在用的兜底方案(#29532 评论里多人在用同名 block_log_inserts trigger)。

1. 确诊:TRACE 是不是元凶

1
sqlite3 ~/.codex/logs_2.sqlite "SELECT level, COUNT(*) FROM logs GROUP BY level ORDER BY COUNT(*) DESC;"

实测里 TRACE 能占到五到七成行数,大头是 target=log 的 websocket / tungstenite 噪声。再隔 30 秒采两次 MAX(id),看还在不在涨:

1
2
3
sqlite3 ~/.codex/logs_2.sqlite "SELECT MAX(id) FROM logs;"
ls -l ~/.codex/logs_2.sqlite-wal
# 等 30 秒,重复上面两条

id 一次响应能跳几千、或每分钟稳定涨一两千,就是正在被持续写盘。

别想着用 RUST_LOG=info 来压——实测设了它,TRACE / DEBUG 照样落进 SQLite(#29532)。这条持久化路径不吃环境变量的过滤。

2. 先备份

即使 Codex 在跑也能做一致性备份:

1
sqlite3 ~/.codex/logs_2.sqlite ".backup '$HOME/.codex/logs_2.sqlite.bak'"

3. 用 trigger 拦住 logs 表的 insert

拦普通表的 insert 要用 BEFORE INSERT + RAISE(IGNORE)不是 INSTEAD OF(那个只能建在视图上,照抄会报错)。

1
sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"

之后所有往 logs 表的写入都会被静默忽略(不报错),从源头止住。trigger 是库级的,对 Codex 已经打开的连接也会即时生效。

4. Checkpoint 并截断 WAL,把空间收回来

1
sqlite3 ~/.codex/logs_2.sqlite "PRAGMA wal_checkpoint(TRUNCATE);"

返回第一列是 0 表示截断成功;如果是 1(busy),说明 Codex 还占着连接,彻底退出 Codex 再跑一次。

5. 采样确认彻底不涨了

1
2
sqlite3 ~/.codex/logs_2.sqlite "SELECT MAX(id) FROM logs;"   # 隔 30 秒两次,数值应不变
ls -l ~/.codex/logs_2.sqlite-wal # 应停在很小,不再增长

MAX(id) 钉死不动、-wal 不再变大,就说明止血成功。

这是侵入式的临时手段,升级到 0.142.0 仍是第一步(它确实砍掉了一大块)。等官方把 macOS App 的 target=log 缺口也补上后,记得把 trigger 删掉还原,否则诊断日志会一直写不进去:

1
sqlite3 ~/.codex/logs_2.sqlite "DROP TRIGGER IF EXISTS block_log_inserts;"

另外 trigger 只对 logs 表下手,别对 state_5.sqlite 等状态库这么干。

让 AI 代跑整套流程

如果你用的是 Claude Code 或 Codex 自己,把这段丢给它,让它代跑上面的步骤:

1
2
3
4
5
排查 ~/.codex/logs_2.sqlite 是否因 TRACE 日志持续高频写盘:
1) SELECT level, COUNT(*) FROM logs GROUP BY level ORDER BY COUNT(*) DESC; 看 TRACE 是否占大头
2) 隔 30 秒采两次 SELECT MAX(id) FROM logs; 和 logs_2.sqlite-wal 文件大小,确认是否还在涨
中招的话:先用 .backup 备份 → 建 BEFORE INSERT ... RAISE(IGNORE) trigger 拦 logs 表 insert →
PRAGMA wal_checkpoint(TRUNCATE) 回收 WAL → 再采样确认 MAX(id) 和 WAL 都不再增长。

桌面 App 用户特别注意事项

如果你用的是 Codex 桌面 App 而非命令行,注意以下几点:

  • App 走独立版本线(最新 26.616,2026-06-18 发布,早于 CLI 的 0.142.0 修复),内嵌自己管理的引擎——所以 npm 升级 CLI 不一定能让 App 用上这个修复
  • 用完彻底退出 App(不是最小化)。App 挂后台跑长任务会持续写盘,退出能立刻止血。
  • 需要时改用已修复的 CLI(0.142.0)干活,功能和 App 高度重叠,是绕开「App 没修」最干净的办法。
  • 清理时**只删 logs_2.sqlite***,别碰 state_5.sqlite 等 state / memories / goals 库——那些含会话状态,删了 App 可能起不来(#24030)。
  • 盯官方更新,等版本号高于 26.616、且写明减少磁盘写入的 App 版本。

常见问题

Q:升级完为什么磁盘空间没回来?
升级只停掉未来的写入,已经占用的空间要手动清(见第三步)。

Q:我已经升到 0.142.0,怎么还在写盘?
因为 0.142.0 只是部分修复。实测在 macOS + Codex 桌面 App(app-server)路径下,TRACE target=log 的高频写入仍未根治(#29532,OPEN,多人复现)。兜底两招:用「进阶」里的 trigger 在线止住 logs 表 insert;别指望 RUST_LOG=info,实测设了照样落库。剩下的盯后续版本。

Q:删掉 logs_2.sqlite 会丢对话记录或配置吗?
不会。它只是诊断日志,会话历史和配置都在别的文件里,不受影响。

Q:暂时升不了级怎么办?
如果暂时升不了级,可以把日志重定向到内存盘(tmpfs),让写入落在内存、重启即清,完全不碰 SSD。思路是把 logs_2.sqlite* 指到 tmpfs(如 Linux 的 /dev/shm/tmp)。日志不含数据,重启丢失无所谓。Windows 没有等价的简单方案,建议直接走升级 + 清理。