警惕 Codex logs_2.sqlite 高频写盘:可能快速消耗 SSD 写入寿命

在这里插入图片描述

最近不少 Codex 用户反馈:Codex 在流式输出和自动化任务期间,会把大量诊断日志持续写入本地 SQLite 数据库,主要文件是:

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

这个问题的危险点不在于 logs_2.sqlite 文件本身看起来有多大,而在于 SQLite WAL 持续写入。主库可能只有几百 MB,底层实际写入量却已经很高。

GitHub 上已有公开 issue 记录了较完整的数据:有人在约 21 天开机时间里观察到主 SSD 写入量增加约 37 TB,按年折算约 640 TB。对于很多 1 TB 消费级 SSD 来说,官方标称 TBW 可能就在 600 TB 左右,这个量级已经值得重视。

相关 issue:

截至 2026-06-23,#28224 已显示有相关 PR 合并并关闭,说明官方方向上已经有修复动作。但不同客户端、不同版本、不同平台未必都已经覆盖到,所以最可靠的做法还是:先检查自己机器,再决定是否临时止血。

这个库里有什么

logs_2.sqlite 不是对话历史库。它主要是 Codex 的本地诊断日志。

我在 Windows 机器上查看到的表结构类似这样:

CREATE TABLE logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    ts INTEGER NOT NULL,
    ts_nanos INTEGER NOT NULL,
    level TEXT NOT NULL,
    target TEXT NOT NULL,
    feedback_log_body TEXT,
    module_path TEXT,
    file TEXT,
    line INTEGER,
    thread_id TEXT,
    process_uuid TEXT,
    estimated_bytes INTEGER NOT NULL DEFAULT 0
);

字段里有 leveltargetfeedback_log_bodymodule_path 等,说明它存的是运行日志、诊断事件和内部 trace 信息。

你的历史对话通常不在这里,而是在类似这些位置:

~/.codex/sessions
~/.codex/archived_sessions
~/.codex/session_index.jsonl
~/.codex/history.jsonl

因此,禁用 logs_2.sqlite 的新增写入,通常不会影响历史对话。它影响的是本地诊断日志。

快速检查自己有没有中招

Windows

先看文件体积:

$base = Join-Path $env:USERPROFILE ".codex"
Get-Item "$base\logs_2.sqlite*" -ErrorAction SilentlyContinue |
  Select-Object Name, Length, LastWriteTime |
  Format-Table -AutoSize

如果你有 sqlite3,继续看日志级别分布:

$db = Join-Path $env:USERPROFILE ".codex\logs_2.sqlite"
sqlite3 $db "SELECT level, COUNT(*) FROM logs GROUP BY level ORDER BY COUNT(*) DESC;"

再看主要来源:

sqlite3 $db "SELECT target, COUNT(*) AS n FROM logs GROUP BY target ORDER BY n DESC LIMIT 15;"

如果看到大量 TRACE,并且 target 里有 responses_websocketsse::responseshyper_utiltokiocodex_otel 这类高频内部事件,就基本符合这个问题的特征。

我本机检查到的数据是:

logs_2.sqlite      141 MB
logs_2.sqlite-wal   10 MB
logs table rows   43,410

TRACE  25,410
INFO   12,615
DEBUG   4,770
WARN      461
ERROR     154

TRACE 大约占 58%,而且主要来源确实包含 WebSocket、SSE、HTTP client、MCP 连接等内部日志。

Linux / macOS

ls -lh ~/.codex/logs_2.sqlite*
sqlite3 ~/.codex/logs_2.sqlite \
  "SELECT level, COUNT(*) FROM logs GROUP BY level ORDER BY COUNT(*) DESC;"
sqlite3 ~/.codex/logs_2.sqlite \
  "SELECT target, COUNT(*) AS n FROM logs GROUP BY target ORDER BY n DESC LIMIT 15;"

如果想观察 WAL 是否持续增长,可以隔几秒重复查看:

watch -n 2 'ls -lh ~/.codex/logs_2.sqlite*'

临时止血方案:用 SQLite trigger 拦截日志写入

这是目前最简单、最直接的 workaround:给 logs 表加一个 BEFORE INSERT trigger,让后续写入日志时直接忽略。

它不会删除已有日志,只是阻止继续新增。

Windows 执行方式

建议先备份:

$db = Join-Path $env:USERPROFILE ".codex\logs_2.sqlite"
$bak = "$db.bak-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
Copy-Item $db $bak

创建 trigger:

sqlite3 $db "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"

验证 trigger 是否存在:

sqlite3 $db "SELECT name, tbl_name FROM sqlite_master WHERE type='trigger' AND name='block_log_inserts';"

做一次手动插入测试:

sqlite3 $db "SELECT 'before', COUNT(*) FROM logs; INSERT INTO logs (ts, ts_nanos, level, target, feedback_log_body, module_path, file, line, thread_id, process_uuid, estimated_bytes) VALUES (0, 0, 'TEST', 'manual_trigger_test', 'should_be_ignored', NULL, NULL, NULL, 'manual', 'manual', 1); SELECT 'after', COUNT(*) FROM logs; SELECT COUNT(*) FROM logs WHERE level='TEST' AND target='manual_trigger_test';"

预期结果:

before|43410
after|43410
0

也就是插入前后行数不变,测试日志没有写进去。

最后可以整理 WAL:

sqlite3 $db "PRAGMA wal_checkpoint(TRUNCATE);"

再检查文件:

$base = Join-Path $env:USERPROFILE ".codex"
Get-Item "$base\logs_2.sqlite*" -ErrorAction SilentlyContinue |
  Select-Object Name, Length, LastWriteTime |
  Format-Table -AutoSize

我本机执行后,logs_2.sqlite-wal 从约 10 MB 变成了 0 字节;等待数秒复查,行数和 WAL 大小没有继续增长。

Linux / macOS 执行方式

cp ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite.bak-$(date +%Y%m%d-%H%M%S)

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

sqlite3 ~/.codex/logs_2.sqlite \
  "SELECT name, tbl_name FROM sqlite_master WHERE type='trigger' AND name='block_log_inserts';"

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

回滚方式

如果后面你需要恢复本地诊断日志,比如要给官方提交 bug 现场,可以删除这个 trigger:

sqlite3 "$env:USERPROFILE\.codex\logs_2.sqlite" "DROP TRIGGER IF EXISTS block_log_inserts;"

Linux / macOS:

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

如果你做过备份,也可以在退出 Codex 后用备份文件恢复。

另一种方案:把日志放到内存盘

GitHub issue 里也有人用 tmpfs/RAM disk 处理,让日志在内存里写,重启后自动消失。

Linux 示例:

mv ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite.bak
ln -s /tmp/logs_2.sqlite ~/.codex/logs_2.sqlite

这个方案的优点是完全不写 SSD,缺点是需要处理数据库初始化、符号链接和进程占用问题。Windows 上也可以用 RAM disk 工具实现,但维护成本会更高。

对大多数用户来说,trigger 方案更简单:可验证、可回滚、改动范围小。

注意事项

这个方案不是官方修复,只是绕过本地日志写入。

它的代价是:Codex 后续不能继续把诊断日志写入 logs_2.sqlite。如果你需要向官方提交复杂 bug,本地持久化日志可能不完整。

另外,Codex 更新后如果重建了 logs_2.sqlite,trigger 可能会消失。建议升级或重装后重新检查:

$db = Join-Path $env:USERPROFILE ".codex\logs_2.sqlite"
sqlite3 $db "SELECT name FROM sqlite_master WHERE type='trigger' AND name='block_log_inserts';"

如果查不到 block_log_inserts,说明需要重新创建。

结论

这次问题的本质是:Codex 的本地 SQLite 诊断日志可能在正常使用时持久化大量 TRACE/DEBUG 级别事件,尤其在流式响应、WebSocket/SSE、MCP、HTTP client 等路径上,导致 WAL 高频写盘。

最稳妥的处理顺序是:

  1. 先升级 Codex 到最新版本。
  2. 检查 ~/.codex/logs_2.sqlite* 是否仍持续增长。
  3. 如果仍有大量 TRACE 写入或 WAL 高频增长,创建 block_log_inserts trigger。
  4. 做插入测试和 WAL 检查,确认写入已被拦截。
  5. 保留回滚命令,等官方修复覆盖到本地版本后再决定是否恢复日志。

这类问题最容易被忽略,因为文件大小不一定暴涨,SSD 写入量却可能已经被 WAL、插入、删除、checkpoint 和文件系统写放大悄悄拉高。发现卡顿、风扇异常、磁盘活动高的时候,值得先查一眼这个库。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐