最近如果你一直开着 Codex,或者经常让它跑流式任务、长时间任务,建议立刻检查一下本地这个文件:

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

我自己的机器已经确认中招:Codex 在流式任务期间,会把大量 TRACE 级别日志持续写入 SQLite,本地 WAL 文件也跟着高频写盘。

这不是“日志多一点”的问题。公开反馈里,已经有人把这个问题估算到约 640 TB/year 的写入量级;也有人反馈 logs_2.sqlite-wal 增长到几十 GB,甚至出现磁盘空间被耗尽。

这篇文章给你一套可以直接照做的流程:

  1. 怎么判断自己有没有中招;
  2. 中招后怎么先备份;
  3. 怎么用 SQLite trigger 临时拦截写入;
  4. 怎么截断 WAL;
  5. 怎么确认 MAX(id) 和 WAL 不再增长。

提醒:下面的 trigger 是紧急止血方案,会让 Codex 本地 logs 表停止新增日志。它适合先保 SSD、保空间,不适合作为长期排障配置。


一、公开反馈已经不少

这个问题不是个例。

在 OpenAI Codex 的 GitHub issue 里,已经有多条类似反馈:

  • issue #28224:Codex SQLite feedback logs can write ~640 TB/year and rapidly consume SSD endurance
  • issue #29463:Windows Codex app 持续把高频 TRACE websocket 日志写入 ~/.codex/logs_2.sqlite
  • issue #17320:streaming 期间 TRACE 日志导致 SQLite WAL 过量写入,并且没有被 RUST_LOG 正常压住
  • issue #28997:logs_2.sqlite-wal 可以无界增长到几十 GB

OpenAI Community 也有人反馈:Codex 开着不管,磁盘空间被日志耗尽。

Notebookcheck 甚至已经用了这样的标题:OpenAI Codex has a critical bug that could kill your SSD in under a year。

请添加图片描述

这些反馈指向的是同一个风险链路:

流式事件很多
→ TRACE / websocket / tungstenite 日志被持久化
→ 写入 SQLite logs 表
→ WAL 高频增长
→ SSD 写入量异常

二、我这台机器的检测结果

我本机检测到的情况很典型。

最近 60 秒窗口里:

TRACE  1579 条
INFO    580 条
DEBUG    26 条
WARN      5 条
ERROR     3 条

短间隔采样中,MAX(id) 曾在 3 秒内增加 95 条。

日志内容主要集中在:

  • websocket
  • tokio-tungstenite
  • response.completed
  • 流式响应 payload

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这基本就是“流式任务期间高频 TRACE 写盘”的典型形态。


三、先检查文件大小

Windows PowerShell:

Get-ChildItem -Force $HOME\.codex |
  Where-Object { $_.Name -like 'logs_2.sqlite*' } |
  Select-Object FullName,Length,LastWriteTime |
  Format-Table -AutoSize

macOS / Linux:

ls -lh ~/.codex/logs_2.sqlite*

重点看这几个文件:

logs_2.sqlite
logs_2.sqlite-wal
logs_2.sqlite-shm

如果 logs_2.sqlite-wal 一直变大,尤其是在你和 Codex 对话、跑任务、流式输出时快速增长,就要继续往下查。


四、统计日志等级

如果你本机有 sqlite3,直接查:

sqlite3 ~/.codex/logs_2.sqlite "
select level, count(*) as n, min(id), max(id), min(ts), max(ts)
from logs
group by level
order by n desc;
"

如果没有 sqlite3,用 Python 自带的 SQLite 模块:

import sqlite3
from pathlib import Path

p = Path.home() / ".codex" / "logs_2.sqlite"
con = sqlite3.connect(f"file:{p}?mode=ro", uri=True, timeout=2)
cur = con.cursor()

for row in cur.execute("""
    select level, count(*) as n, min(id), max(id), min(ts), max(ts)
    from logs
    group by level
    order by n desc
"""):
    print(row)

print("max id:", cur.execute("select max(id) from logs").fetchone()[0])
con.close()

如果 TRACE 占比很高,而且最新日志集中在 websocket、tungstenite、SSE、response payload,就很可疑。


五、短间隔采样:看它是不是还在涨

只看总量还不够,关键是看它现在是不是仍在增长。

运行下面脚本:

import sqlite3, time
from pathlib import Path

p = Path.home() / ".codex" / "logs_2.sqlite"
wal = Path(str(p) + "-wal")

def sample():
    con = sqlite3.connect(f"file:{p}?mode=ro", uri=True, timeout=2)
    cur = con.cursor()
    max_id = cur.execute("select max(id) from logs").fetchone()[0]
    con.close()
    wal_size = wal.stat().st_size if wal.exists() else 0
    return max_id, wal_size

for i in range(5):
    print(i, sample())
    time.sleep(3)

如果几次采样里:

  • MAX(id) 一直变大;
  • logs_2.sqlite-wal 一直变大;
  • 同时 Codex 正在跑流式任务;

那就不要犹豫,先止损。


六、中招后的止损流程

核心思路很简单:

先备份
→ 再拦截 logs 表写入
→ checkpoint/truncate WAL
→ 再采样确认不增长

第一步:备份数据库

不要直接粗暴复制主库文件。SQLite 可能还有 WAL 中的已提交内容没有 checkpoint。

用 Python online backup:

import sqlite3, shutil, time
from pathlib import Path

src = Path.home() / ".codex" / "logs_2.sqlite"
stamp = time.strftime("%Y%m%d-%H%M%S")
backup = src.with_name(f"logs_2.sqlite.backup-before-trigger-{stamp}")

ro = sqlite3.connect(f"file:{src}?mode=ro", uri=True, timeout=10)
bk = sqlite3.connect(str(backup), timeout=10)
with bk:
    ro.backup(bk)
bk.close()
ro.close()

for suffix in ["-wal", "-shm"]:
    side = Path(str(src) + suffix)
    if side.exists():
        dst = src.with_name(f"logs_2.sqlite{suffix}.backup-before-trigger-{stamp}")
        shutil.copy2(side, dst)

print("backup:", backup)

这一步会生成数据库备份,也会把 WAL/SHM sidecar 一起留存。

第二步:用 trigger 拦截写入

import sqlite3
from pathlib import Path

p = Path.home() / ".codex" / "logs_2.sqlite"
con = sqlite3.connect(str(p), timeout=10)
cur = con.cursor()

cur.execute("""
CREATE TRIGGER IF NOT EXISTS codex_stop_logs_insert
BEFORE INSERT ON logs
BEGIN
    SELECT RAISE(IGNORE);
END;
""")

con.commit()
print("trigger installed")
print(cur.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchall())
con.close()

这条 trigger 会让后续写入 logs 表的 INSERT 被忽略。

换句话说:Codex 还可以运行,但本地日志表不会继续增长。

代价是:后续本地日志会丢失。

第三步:确认 WAL 被截断

再看一次文件大小:

ls -lh ~/.codex/logs_2.sqlite*

Windows PowerShell:

Get-ChildItem -Force $HOME\.codex |
  Where-Object { $_.Name -like 'logs_2.sqlite*' } |
  Select-Object FullName,Length,LastWriteTime |
  Format-Table -AutoSize

理想情况下,logs_2.sqlite-wal 会被截断到 0 或非常小。

第四步:连续采样确认不再增长

import sqlite3, time
from pathlib import Path

p = Path.home() / ".codex" / "logs_2.sqlite"
wal = Path(str(p) + "-wal")

def sample(i):
    con = sqlite3.connect(f"file:{p}?mode=ro", uri=True, timeout=2)
    cur = con.cursor()
    row = cur.execute("select max(id), count(*) from logs").fetchone()
    trig = cur.execute("""
        select count(*)
        from sqlite_master
        where type='trigger'
          and name='codex_stop_logs_insert'
    """).fetchone()[0]
    con.close()

    print({
        "i": i,
        "max_id": row[0],
        "rows": row[1],
        "wal_size": wal.stat().st_size if wal.exists() else 0,
        "trigger": trig,
    })

for i in range(6):
    sample(i)
    time.sleep(3)

如果你看到:

MAX(id) 不再增长
rows 不再增长
wal_size 不再增长
trigger = 1

说明止血成功。


七、升级后怎么恢复日志?

如果你后续升级到了包含修复的 Codex 版本,或者你需要恢复本地日志,可以删除 trigger:

import sqlite3
from pathlib import Path

p = Path.home() / ".codex" / "logs_2.sqlite"
con = sqlite3.connect(str(p), timeout=10)
cur = con.cursor()
cur.execute("DROP TRIGGER IF EXISTS codex_stop_logs_insert")
con.commit()
print(cur.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchall())
con.close()

恢复后不要马上放心。

建议继续跑几分钟流式任务,再采样:

MAX(id) 是否快速增长?
logs_2.sqlite-wal 是否快速增长?
TRACE 是否又占了大头?

如果问题复现,就继续保留 trigger,或者等待更明确的上游修复版本。


八、上游修复正在推进

公开信息里可以看到,相关 PR 已经出现:

  • PR #29432:避免持久化完整 response payload。
  • PR #29457:减少带有 tool/stdout 等内容的 OTEL event 持久化。
  • PR #29599:继续处理 raw event level 本地遥测持久化问题。

issue #28224 的描述也提到,部分 PR 已进入 0.142.0,反馈中称可以减少大量日志。

但我建议不要只看版本号。

真正可靠的判断是:

升级以后,再跑一次采样。
MAX(id) 不快速增长,WAL 不快速增长,才是真的安心。

最后说两句

这篇文章不是说“Codex 一定会写坏所有 SSD”。

更准确地说,它提醒的是一个已经被多方反馈、也能本机复现的风险形态:

在 streaming 和长时间运行场景下,Codex 本地 SQLite 日志可能出现高频 TRACE 写入;如果持续存在,会造成异常磁盘写入和 WAL 膨胀。

AI 工具越强,我们越容易把它开着不管。

但本地机器不是无限资源。CPU、内存、网络、磁盘、SSD 寿命,都是需要被尊重的边界。

如果你正在用 Codex,今天就花一分钟查一下:

~/.codex/logs_2.sqlite-wal

它安静,你就安心。

它狂涨,就先止损。



文末互动

你可以把这篇文章转给正在使用 Codex、Claude Code、Cursor、VS Code Agent 插件或其他本地 AI 编程工具的朋友

codex使用方法大全橙皮书下载点击查看
链接:https://pan.quark.cn/s/c4b28b23db05

Logo

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

更多推荐