这个问题已经存在很久了,官方到现在仍然没有彻底修复。

Powered by Codex & OWL
Version 26.707.62119

我是 macOS 上的 Codex / ChatGPT 桌面端,打开以后,它会把大量本地运行日志写进 ~/.codex/logs_2.sqlite,同时不断更新 SQLite 的 WAL 文件。多次升级以后,这个现象还在。RUST_LOG=warn、关闭 analytics 这些办法,我这边都没有稳定挡住 SQLite 里的 TRACE 日志落盘。

其他操作系统按照文中方法自己测试一下,估计大概率存在。

我最后采用的办法是:运行时把日志库放到 RAM disk,退出后再 checkpoint、裁剪、压缩、回写到默认位置。它不是官方修复,但至少能把高频写入从 SSD 挪走。

问题

我机器上打开日志库的进程是桌面版Codex / ChatGPT:

/Applications/ChatGPT.app/.../codex app-server

相关文件在这里:

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

logs_2.sqlite 是主库,-wal 是 SQLite write-ahead log,-shm 是辅助文件。正常应用写一点诊断日志没什么。麻烦在于 Codex 会把很细的 TRACE 级别内容也持久化下来,包括 HTTP transport、streaming、WebSocket/SSE、token delta、工具调用等。

我见过比较夸张的情况:

logs_2.sqlite: 2GB+
最近 60 秒:
TRACE 900+
INFO  80+
DEBUG 50+

还有单条日志几 MB 的情况:

target: codex_http_client::transport
level: TRACE
single row: 2MB 到 7MB+

所以有时候你执行了 VACUUM,文件还是不怎么变小,不一定是压缩没生效。可能只是最近保留下来的 TRACE 本身就很大。

影响

第一是 SSD 写入和寿命。SQLite WAL 本身没问题,但 TRACE 太密,logs_2.sqlite-wal 就会长期活跃。SSD 的寿命通常按 TBW,也就是总写入字节数来估算。这个问题不会让 SSD 立刻坏掉,但它会制造额外写入,长期挂着 Codex 时就在慢慢消耗写入额度。写入速度越高、开机时间越长、SSD 本身 TBW 越低,越不值得忽视。

这里容易被低估的一点是:你看到的 logs_2.sqlite 只有几 GB,不代表实际写入也只有几 GB。SQLite WAL、checkpoint、清理、重写、文件系统元数据、备份和索引都可能放大真实写入量。尤其是 TRACE 日志持续进来又被清理时,文件大小可能变化不大,但 SSD 仍然在忙。

第二是空间。logs_2.sqlite 可以滚到几百 MB,甚至几个 GB。如果 WAL 没及时 checkpoint/truncate,-wal 也会一起占空间。

第三是机器负担。持续 SQLite 写入会带来 I/O、CPU wakeup、文件系统事件、备份和索引干扰。笔记本上可能表现为发热、电量掉得快,或者磁盘活动看起来一直停不下来。即使 SSD 寿命不是你最担心的点,这些后台小写入也会让机器没那么安静。

第四是日志内容。TRACE 日志不是你的会话正文,但里面可能有路径、请求片段、响应片段、工具调用和上下文摘要。排查问题时可以看,不要随便把整个 logs_2.sqlite 发出去。

让 Codex 自己测,也让它自己修

可以直接复制:

请帮我检测并修复 Codex 的高频 SQLite 日志写盘问题。默认日志库是 ~/.codex/logs_2.sqlite,包含 -wal 和 -shm。

先检测问题是否存在:
1. 显示 ~/.codex/logs_2.sqlite、-wal、-shm 的大小。
2. 运行 lsof ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite-wal ~/.codex/logs_2.sqlite-shm,确认是否有 Codex 正在打开。
3. 做 15 秒采样:比较日志库和 WAL 的 mtime/大小变化,并查询 logs 表的 count(*)、max(id)、TRACE/DEBUG/INFO 数量变化。
4. 查询最近 60 秒各 level 的日志数量。
5. 查询 estimated_bytes 最大的 20 条日志,看看是否是 TRACE / codex_http_client::transport。

如果问题不存在,只说明检测结果,不要改文件。

如果问题存在,且当前没有活跃 SQLite 句柄,请把 Codex 的高频 SQLite 日志写入改成 RAM disk 往返模式,目标是减少 SSD 写入,同时在退出 Codex 后把日志安全复制回默认位置。如果当前有活跃句柄,不要改文件,告诉我先退出 Codex,再继续。

要求:
1. 默认日志库是 ~/.codex/logs_2.sqlite,包含 -wal 和 -shm。
2. 不要热迁移活跃 SQLite。如果 lsof ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite-wal ~/.codex/logs_2.sqlite-shm 显示 Codex 正在使用,脚本必须拒绝迁移,并提示我先退出 Codex。
3. 写一个脚本 ~/.codex/bin/codex-logs-ramdisk,支持:
   - status:显示默认日志路径是否为 symlink、RAM disk 是否挂载、是否有活跃句柄。
   - apply:创建 RAM disk,把现有 ~/.codex/logs_2.sqlite checkpoint 后复制到 RAM disk,再把默认路径替换成指向 RAM disk 的 symlink。
   - restore / flush:在 Codex 退出后,对 RAM disk 里的 SQLite 执行 PRAGMA wal_checkpoint(TRUNCATE),裁剪旧日志,VACUUM INTO 压缩,复制回 ~/.codex/logs_2.sqlite,恢复成普通磁盘文件,并卸载 RAM disk。
   - compact:在 Codex 完全退出且默认库是普通磁盘文件时,checkpoint、裁剪旧日志、VACUUM INTO 压缩。若有活跃句柄必须拒绝。
   - run:执行 apply,打开真正的 /Applications/ChatGPT.app 或 /Applications/Codex.app,等待它退出,然后自动 restore。
   - watch:等待日志库被打开过,再等待关闭,然后自动 restore 一次。
4. RAM disk 默认 3072 MiB,卷名默认 CodexLogsRam,允许用环境变量 CODEX_LOGS_RAMDISK_MB 和 CODEX_LOGS_RAMDISK_NAME 覆盖。
5. 日志默认只保留最近 1 天,允许用 CODEX_LOGS_RETENTION_DAYS 覆盖;设为 0 时只压缩,不删除 logs 表记录。
6. 在 ~/.codex/config.toml 中加入:
   [analytics]
   enabled = false
   如果已有 [analytics],只设置或合并 enabled = false,不要破坏其它配置。
7. 创建一个包装 App:~/Applications/Codex RAM Logs.app。
   - 它不要修改原始 /Applications/ChatGPT.app 或 /Applications/Codex.app。
   - 可复用原 App 的 electron.icns 图标。
   - 它的可执行脚本调用 ~/.codex/bin/codex-logs-ramdisk run。
   - stdout/stderr 写到 ~/.codex/codex-ram-logs-wrapper.log。
   - bundle id 可用 local.<username>.codex-ram-logs。
8. 验证:
   - zsh -n ~/.codex/bin/codex-logs-ramdisk 通过。
   - plutil -lint ~/Applications/Codex\ RAM\ Logs.app/Contents/Info.plist 通过。
   - 用临时 CODEX_HOME 和测试 RAM disk 做 roundtrip:磁盘旧日志 -> RAM -> 新写入 -> restore 回磁盘,确认行数和内容都保留。
   - 当前 Codex 活跃时,apply、restore、compact 应安全拒绝,不能移动或压缩正在打开的库。
   - 启动后用 lsof 确认 Codex 打开的是 /Volumes/CodexLogsRam/logs_2.sqlite,而不是 SSD 上的 ~/.codex/logs_2.sqlite。
9. 最终说明以后应打开 Codex RAM Logs.app,退出 Codex 时用 Cmd+Q。脚本会等进程真正退出后再 restore。重启或重新启动后,我会再问“问题修复了吗?再检测一下”,你需要用 status、lsof 和 15 秒采样确认写入是否已经落到 RAM disk。

这段 prompt 的重点不是让 Codex 盲目改文件,而是让它按顺序来:先测,能安全修就修,不能安全修就停。lsof 里还有活跃句柄时,不要硬来。

这段 prompt 是针对MacOS的,你可以让Codex采用这样策略适配到你的操作系统。

修复完成后,需要重启,然后再问它问题修复了吗就可以。

务必让 Codex 教会你怎么用它写的脚本,主要是如何打开和关闭 Codex,避免日志丢失(其实这些日志也没啥,丢了也就丢了)

最终效果:RAM disk 往返

我的目标不是让 Codex 找不到日志路径,而是让它照常写,只是写到 RAM 里。

运行期间:

~/.codex/logs_2.sqlite -> /Volumes/CodexLogsRam/logs_2.sqlite

退出后:

/Volumes/CodexLogsRam/logs_2.sqlite
  -> checkpoint
  -> 删除过期日志
  -> VACUUM INTO 压缩
  -> 回写 ~/.codex/logs_2.sqlite
  -> 卸载 RAM disk

我给这个方案留了三条底线:

  • 不碰正在使用的 SQLite。
  • RAM disk 里的东西退出后要回写。
  • 旧 TRACE 默认裁剪掉,不让本地诊断日志越滚越大。

脚本在这里:

~/.codex/bin/codex-logs-ramdisk

常用命令:

~/.codex/bin/codex-logs-ramdisk status
~/.codex/bin/codex-logs-ramdisk apply
~/.codex/bin/codex-logs-ramdisk restore
~/.codex/bin/codex-logs-ramdisk compact
~/.codex/bin/codex-logs-ramdisk run
~/.codex/bin/codex-logs-ramdisk watch

我平时用包装 App 启动:

~/Applications/Codex RAM Logs.app

它实际调用:

~/.codex/bin/codex-logs-ramdisk run

日常流程很简单:

  1. Codex RAM Logs.app 启动。
  2. 退出时用 Cmd+Q
  3. 等脚本自动 restore。

如果 Codex 已经退出,想手动压缩:

~/.codex/bin/codex-logs-ramdisk compact

如果还有活跃句柄,它会停下来,不会硬压正在使用的库。

保留多久的日志

我一开始保留 7 天,后来发现没什么意义。最近 7 天本身就可能有 2GB TRACE。现在默认只保留最近 1 天:

CODEX_LOGS_RETENTION_DAYS=1

我自己的取舍是:这些 TRACE 日志平时价值很低,保留 1 天足够排查刚发生的问题。session 对话不在 logs 表里,裁剪日志不会删掉会话正文。

退出和重启

正常退出用 Cmd+Q。退出后理想状态是:

default_db_symlink: no
ramdisk_mounted: no
active_handles: no

如果退出后还是这样:

default_db_symlink: yes
ramdisk_mounted: yes
active_handles: no

可以手动 restore:

~/.codex/bin/codex-logs-ramdisk restore

系统重启或断电时,RAM disk 里的本次运行日志可能丢失。对我来说可以接受,因为丢的是本地诊断日志,不是 session 对话。

RAM disk 容量要给够。如果 logs_2.sqlite 已经 2GB,就不要只给 1GB。我现在默认用 3072 MiB:

CODEX_LOGS_RAMDISK_MB=3072

如果还不够,可以临时提高:

CODEX_LOGS_RAMDISK_MB=4096 ~/.codex/bin/codex-logs-ramdisk run

最后怎么取舍

这不是一个优雅方案。真正应该做的是官方修:降低持久化日志级别,限制日志体积,加日志轮转或压缩,提供明确配置项。

但在那之前,我不想让本地 TRACE 日志一直打 SSD。RAM disk 往返模式至少把高频写入挪到了内存里,退出时再保守地回写。对我来说,这是现在比较能接受的折中。

参考链接