Codex 正在悄悄写穿你的SSD?排查与修复指南

作者:技术拾光

时间:2026-08-03

浏览:667

OpenAICodexCLI存在隐蔽的SSD写入问题,默认将TRACE级别网络事件写入~/.codex/logs_2.sqlite,21天可产生约37TB写入量,年化近640TB,超过主流1TBSSD标称寿命。根源是两条独立日志过滤链导致RUST_LOG设置无效。提供诊断与修复步骤,包括立即止血方案及RAMDisk、tmpfs正确用法。

文章目录
先说几个核心判断:OpenAI Codex CLI 确实存在一个相当隐蔽的 SSD 写入问题。它会在你毫无察觉的情况下,默认把 TRACE 级别的网络事件持续写入 ~/.codex/logs_2.sqlite 数据库。21 天就能产生大约 37TB 的写入量,年化下来接近 640TB——这已经超过了主流 1TB 消费级 SSD 标称的全生命周期写入寿命(通常是 600TBW 左右)。

这个问题最早由 Apache Flink PMC 成员 @1996fanrui 在 2026 年 6 月于 GitHub 上提交了缺陷报告,随后引发了社区的广泛关注。下面这篇文章会从诊断到根治,给你完整的操作步骤,包括立即止血的方案、官方配置的迁移方法,以及 macOS RAM Disk 和 Linux tmpfs 的正确用法。

Codex 正在悄悄写穿你的SSD?排查与修复指南


为什么会这样?根本原因

问题的根源在于,Codex 内部使用了两条独立的日志过滤链。

你通过 RUST_LOG=warn 设置的过滤器,实际上只管得住终端输出。而 app-server 和 TUI 启动时挂载的 SQLite 日志层,用的是自己的一套过滤链,默认接受所有 TRACE 级别:

// Codex 源码中的默认配置
.with_filter(Targets::new().with_default(Level::TRACE))

结果就是,即便你设置了 RUST_LOG=warn,TRACE 日志依然绕开这个限制,每秒都在持续落盘。更麻烦的是,每次插入还会更新多个索引,加上保留策略的定期删除,就形成了一个持续的 insert-prune 循环。你看到文件大小可能稳定在几百 MB,但 SSD 背后承受的写入量却在悄悄累积(来源:GitHub Issue #17320,2026 年 6 月)。

受影响的主要是这三个文件:

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

第一步:先诊断,确认你中招了没

# 查看文件大小(超过几百 MB 就要警惕)
ls -lh ~/.codex/logs_2.sqlite*

# 查看日志级别分布,确认是否有大量 TRACE
sqlite3 ~/.codex/logs_2.sqlite 
  "SELECT level, COUNT(*) FROM logs GROUP BY level ORDER BY COUNT(*) DESC;"

# 找出写入量最高的来源(前 20 条)
sqlite3 ~/.codex/logs_2.sqlite <<'SQL'
SELECT target, level, COUNT(*) AS n
FROM logs
GROUP BY target, level
ORDER BY n DESC
LIMIT 20;
SQL

如果 TRACE 行数以百万计,说明问题已经相当严重,需要立即执行下面的方案。


方案一:SQLite Trigger 立即止血(推荐先做)

这是一个最快速的临时方案。注意,先完全退出 Codex,再执行:

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

验证是否生效:

sqlite3 ~/.codex/logs_2.sqlite 
  "SELECT name FROM sqlite_master WHERE type='trigger';"

如果看到 block_log_inserts 就说明成功了。之后新启动的 Codex 将不再向该文件写入日志。

需要提醒的是:Codex 版本升级后,要重新检查 trigger 是否还存在,因为升级有可能重建数据库文件。


方案二:把 SQLite 迁走(官方配置,长期有效)

这是 Codex 官方文档提供的正规路径——通过 sqlite_home 配置项,把数据库整体迁移到其他位置,比如外置硬盘、HDD 或内存盘。

方式 A:写进配置文件(永久生效)

# ~/.codex/config.toml
sqlite_home = "/Volumes/ExternalDisk/codex-sqlite"

方式 B:环境变量(单次生效,适合测试)

CODEX_SQLITE_HOME=/path/to/other/disk codex

这里要特别说明:sqlite_home 会迁移所有 SQLite 状态,包括 state、logs、goals、memories,不只是日志。迁移前务必确认目标路径可写且空间充足。


方案三:macOS RAM Disk(内存当硬盘用)

把 SQLite 文件放进内存盘,写入全在内存里完成,SSD 完全不受影响。代价是重启后数据清空,适合不需要跨会话保留日志的场景。

# 创建约 1 GiB RAM Disk
DISK=$(hdiutil attach -nomount ram://2097152 | awk 'NR==1 {print $1}')
diskutil erasevolume HFS+ CodexRAM "$DISK"
mkdir -p /Volumes/CodexRAM/codex-sqlite

# 用 RAM Disk 运行 Codex
CODEX_SQLITE_HOME=/Volumes/CodexRAM/codex-sqlite codex

# 用完后卸载
hdiutil detach /Volumes/CodexRAM

一个常见的误区:macOS 的 /tmp 不是 tmpfs,软链到 /tmp 对保护 SSD 毫无意义。验证你的 /tmp 是否在内存:

df -h /tmp   # Filesystem 如果是 /dev/disk,就是 SSD,不是内存

方案四:Linux tmpfs(前提是先确认挂载)

Linux 下 /tmp 通常是 tmpfs(内存文件系统),但并非所有发行版都默认如此。先确认:

findmnt /tmp       # 看 FSTYPE 是否为 tmpfs
findmnt /dev/shm   # /dev/shm 通常一定是 tmpfs

确认是 tmpfs 后,再通过配置文件或环境变量把 sqlite_home 指向它:

# ~/.codex/config.toml(Linux,/tmp 已确认为 tmpfs)
sqlite_home = "/tmp/codex-sqlite"

或者指向更稳的 /dev/shm:

mkdir -p /dev/shm/codex-sqlite
CODEX_SQLITE_HOME=/dev/shm/codex-sqlite codex

同时关掉 history 持久化(附加减量)

除了 SQLite 问题,Codex 还会把会话记录写进 history.jsonl。如果不需要保留历史,可以关掉它,并限制文件大小上限(来源:Codex 官方配置文档,2026 年):

# ~/.codex/config.toml
[history]
persistence = "none"      # 关闭 session 记录
max_bytes = 10485760      # 或限制为 10MB 上限

两项结合,可以把 Codex 在 ~/.codex 目录下产生的写入量降到最低。


安全清理已有日志

别直接删 ~/.codex 整个目录——里面有你的登录凭据和配置文件。只删日志:

# 1. 先确认 Codex 已完全退出
pgrep -fl 'Codex|codex|app-server'
lsof -nP | grep -E '.codex/.+logs_2.sqlite'

# 2. 备份后移除(安全起见先备份)
BACKUP="$HOME/.codex/logs-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP"
mv "$HOME/.codex"/logs_2.sqlite* "$BACKUP"/ 2>/dev/null
echo "已备份到 $BACKUP"

如果 Time Machine 或 rsync 在备份 ~/.codex,记得排除 WAL 文件:

--exclude='*.sqlite-wal'
--exclude='*.sqlite-shm'

那些看起来有用、其实没用的方法

方案为什么无效
RUST_LOG=warn只影响终端输出,SQLite 日志层独立过滤,TRACE 照写不误
[feedback] enabled = false只禁用反馈上传,日志写入层不受影响
定期清理旧行 + VACUUM能回收文件空间,但不阻止新写入,SSD 写放大依然存在

常见问题

Q:我的 SSD 已经因此损耗了,怎么评估损失?

在 macOS 用 smartmontools(brew install smartmontools),然后运行 smartctl -a /dev/disk0,看 Total bytes written 字段。对比购买时的 TBW 规格,可以算出剩余寿命百分比。Linux 用 nvme-cli:sudo nvme smart-log /dev/nvme0,看 Data Units Written(单位 512B)。

Q:关掉 trigger 后,Codex 还能正常用吗?

可以。Trigger 只阻断日志写入,不影响 Codex 的代码生成、Agent 任务、会话管理等核心功能。唯一的损失是 SQLite 里不再保存运行日志,调试时无法通过 sqlite3 查历史记录。

Q:OpenAI 官方会修复这个问题吗?

GitHub Issue #17320 已标记为 bug,截至 2026 年 6 月 30 日尚未关闭。从根本修复的角度,需要在 SQLite 日志层加入独立的 level 过滤,或默认将日志级别从 TRACE 提升到 WARN。在官方修复前,sqlite_home 迁移方案是最稳妥的长期解决方式。


权威来源:

  • Codex 官方配置文档 — config-reference
  • Codex 官方文档 — 环境变量
  • GitHub openai/codex Issue #17320(@1996fanrui 提交,2026-06-14)

本文方案基于 2026 年 6 月 30 日 Codex 版本,官方修复后建议优先采用官方方案。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章