Skip to content
Son Tran (Snow)
All posts

Note

Codex CLI's SQLite log and SSD writes

Codex CLI keeps a diagnostic log in a local SQLite database at ~/.codex/logs_2.sqlite, with -wal and -shm files next to it. A GitHub issue, openai/codex #28224, reported that this log was the main continuous writer on an SSD: about 37 TB in roughly 21 days of uptime. This note covers how to check a machine, what the fixes changed, and one detail of the popular workaround that does not carry over to macOS.

What was reported

  • The volume. About 37 TB written in roughly 21 days of uptime, which extrapolates to about 640 TB a year. Some 1 TB consumer SSDs are rated for around 600 TBW (terabytes written).
  • The cause, by the reporter's diagnosis. The persistent log sink was installed with a global TRACE default, the most verbose level.
  • What filled the log. In the reporter's sample, TRACE was 70.7% of the retained bytes. Raw WebSocket payloads from a single target, codex_api::endpoint::responses_websocket, were the largest source: 527 MiB of about 1,036 MiB retained.
  • Why the file size misleads. The database stayed around 1 GiB, but a 15-second sample showed about 36,211 rows inserted while the retained row count did not change. Rows are written, indexed, appended to SQLite's write-ahead log (WAL), and then pruned. The row id counter had passed 5.5 billion while about half a million rows were retained.
  • Privacy. The reporter kept raw log bodies out of the issue because the WebSocket payloads may contain private conversation content. Several write-ups say the file holds no conversation data; the reporter's caution points the other way.

Checking a machine

File size alone is a weak signal, for the reason above. Two readings of the highest row id, a minute apart, say more: if MAX(id) climbs while the row count stays flat, rows are being written and pruned.

ls -lh ~/.codex/logs_2.sqlite*
codex --version
sqlite3 -readonly ~/.codex/logs_2.sqlite "SELECT COUNT(*), MAX(id) FROM logs;"

The query opens the live database read-only. It was not tested for this note, and one issue in the tracker advises inspecting a copy of the files instead.

What the fixes changed

As of June 24, 2026, three pull requests had been merged:

  • #29432 stops logging every Responses WebSocket event (released in 0.142.0, June 22).
  • #29457 filters noisy targets out of the persistent logs (released in 0.142.0).
  • #29599 stops persisting bridged log events (merged June 23, scheduled for 0.143.0).

The reporter estimated that the three together avoid about 85% of the log volume and wrote on June 23 that the issue could be closed. On June 24, the third fix was not yet in a release.

Later

The thread kept going for weeks (read on October 30, 2026):

  • July 1. Stable 0.142.5 carried a separate WebSocket trace fix (#30757, backported in #30771) but not #29599.
  • July 8. 0.143.0 was published with #29599. A commenter reported only 2 of 189 log rows at TRACE in one session.
  • Mid-July. One user still measured about 7 row ids per second on a desktop build bundling 0.144.0-alpha.4, down from about 33 on 0.142.5.
  • End of the thread. A member of the OpenAI team wrote that the causes had been addressed and closed the issue as fixed.
  • A separate issue, #32496, reports another source of avoidable writes: models_cache.json and scope_v3.json being rewritten continually.

The /tmp symlink

The workaround quoted in most write-ups:

ln -sf /tmp/logs_2.sqlite ~/.codex/logs_2.sqlite

The write-ups describe it as sending the writes to memory. That holds where /tmp is a RAM-backed tmpfs, which some Linux distributions mount. On macOS, /tmp is a symlink to /private/tmp, a directory on the internal data volume. The file moves, and the writes still reach the SSD. df /tmp shows which device backs the directory.

Two options from the thread stop the writes themselves.

SQLite trigger. A trigger that drops TRACE rows before they are stored, installed while Codex is quit:

sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS codex_block_trace_logs BEFORE INSERT ON logs WHEN NEW.level = 'TRACE' BEGIN SELECT RAISE(IGNORE); END;"

Commenters report that DEBUG, INFO, and WARN rows still persist, and that the trigger is lost if Codex recreates the database. Without the WHEN clause it blocks every insert. The statement DROP TRIGGER IF EXISTS codex_block_trace_logs; removes it.

RAM disk. A RAM disk created with hdiutil, with the three SQLite files symlinked into it. It has to be recreated at every boot.

What the listing showed (June 24)

Codex CLI is used only occasionally on this Mac. On June 24, ls -l showed logs_2.sqlite as an 18-byte symlink to /tmp/logs_2.sqlite, created that morning at 10:37. Beside it was a 1.6 MB logs_2.sqlite-wal dated June 9, which predates the symlink.

Limits

  • Apart from the ls output and one df /tmp check, everything comes from the issue thread and public documentation. No log write rate was measured.
  • Whether this Mac was affected is not established: the main database file was never sized, and the leftover -wal file is small.
  • That /tmp sits on the data volume rests on documentation and community threads, plus a check on October 30, 2026: df /tmp reported the internal data volume, /System/Volumes/Data.