Will Codex Write Your SSD to Death?
I Checked My Local Logs
People are sharing a warning: Codex writes to logs_2.sqlite at high frequency in the background. The file does not look large, but actual writes may be high. I first thought it was an exaggerated headline, but after checking my local logs, I changed my mind.
Update 2026-06-24: OpenAI has acknowledged this bug and merged a fix, and #28224 was briefly closed—but was soon reopened, because the latest stable version 0.142.0 still has not fully fixed it. The complete fix is only in 0.143-alpha, released that day. After upgrading this machine to 0.142.0, three tests still showed writes, users on multiple platforms reproduced it, and even OpenAI’s own fix commit identifies the cause—see the section “Just update and it’s fixed?” below.
①What People Are Saying Online
I saw two types of claims: one is reposted screenshots that simply say Codex is burning through disks; the other is lencx’s source-code and issue review.
The reposted version says: after installing Codex CLI, it writes data to ~/.codex/logs_2.sqlite at high frequency; the file does not appear to grow, but SSD writes keep increasing.
The evidence-based version comes from lencx(浮之静)’s “Codex Logs Are Burning Your SSD”. He went through the Codex source code and a series of GitHub issues, pointing the problem at the default configuration of the SQLite logging layer: TRACE-level network events, telemetry mirroring, and dependency-library noise are written into a persistent database, so file size does not represent actual write volume.
I checked my own machine in that direction. Below, I only include local commands and verifiable issues, rather than drawing conclusions from secondhand retellings.
②What I Found on My Own Machine
First, the files:
$ ls -lh ~/.codex/logs_2.sqlite*
logs_2.sqlite 126M
logs_2.sqlite-wal 4.9M
logs_2.sqlite-shm 32K
In the database, 66% is TRACE. The content is not business logs, but low-level instrumentation for websocket polling reads and writes:
$ sqlite3 ~/.codex/logs_2.sqlite "SELECT level,count(*) FROM logs GROUP BY level ORDER BY 2 DESC"
TRACE 65744 -- 66%
INFO 17968
DEBUG 13459
WARN 1974
ERROR 8 -- about 99,000 rows total at the time
The latest few entries looked like this:
TRACE log WouldBlock
TRACE log tokio-tungstenite/compat.rs:157 Read read -> poll_read
TRACE codex_api::endpoint::responses_websocket ...
Looking only at the current row count is not enough. This database writes and deletes at the same time, so its size can mislead you.
The Writer Is the Desktop App, Not the CLI You Type Into
The original post described the problem as “the CLI is writing.” I checked the process_uuid in the logs, and the latest 500 rows all came from the same process:
$ ps -p 2950 -o command
/Applications/Codex.app/Contents/Resources/codex app-server
The writes came from the app-server of Codex desktop(Codex.app). At the time, I did not have an interactive codex terminal open. A more accurate statement is not “the CLI is burning through the disk,” but “a running Codex process writes to this shared SQLite logging layer”; the desktop app, TUI, and editor extensions may all participate.
The File Is Only 126 MB, but It Wrote 8.45 Million Rows in 10 Days
The primary key is an auto-incrementing id. I checked the current id range and actual row count:
$ sqlite3 ~/.codex/logs_2.sqlite "SELECT min(id), max(id), count(*) FROM logs"
min(id) 206,551,383
max(id) 215,003,791 -- auto-increment has reached 215 million
count(*) 101,406 -- but only 100,000 rows remain
Range span: 8.45 million; actual remaining rows: 100,000 => 8.35 million rows have already been deleted
This is an “insert-prune” loop: the table only keeps roughly the most recent 10 days of rows(the oldest row on my side was exactly 10 days old), and old rows are deleted. Seeing a stable 126 MB file with ls does not mean nothing is being written. The auto-incrementing id shows that 8.45 million rows were actually inserted over 10 days, and 8.35 million of them were deleted.
This overturned my first assessment. I had originally estimated based on the “remaining 100,000 rows” and concluded it was “about 7 MB/day,” even calling it clickbait. Calculated from the actual 8.45 million inserted rows, it is about 600 MB/day of log body text, meaning I undercounted by nearly 90x. And this is only logical writes; WAL, four indexes, checkpointing, and deletion itself all write to disk again.
The upstream issues show even larger magnitudes: #17320 measured WAL writes of about 5–16 MiB/s during streaming output, and a single 50-token response made MAX(id) jump by about 5,000 rows; the reporter of #28224 said it wrote about 37 TB over 21 days, extrapolating to about 640 TB/year. My earlier sample where “it only grew by 109 rows in 6 seconds” only captured an idle period; the high-write window is during model output.
"Just update to a newer version"? Not that simple (verified on 2026-06-24)
This needs to be unpacked. OpenAI did merge two noise-reduction PRs in 0.142.0 (released on 2026-06-22), including #29457, and #28224 was closed for a while. But it was soon reopened (the issue's state_reason is now reopened)—because the fix was incomplete: people on macOS, Windows, and Linux all reported that writes continued after upgrading to 0.142.0, and someone measured ~10 MB/s, about 315 TB/year with iotop.
I reproduced it on my own machine too. After upgrading both the CLI and the desktop core to 0.142.0, it was still writing at ~18 lines/second while idle and spiking into the thousands during streaming output, still writing things like TRACE/log and TRACE/hyper_util. Why didn't the partial patch work? OpenAI explained it themselves in the real fix commit, #29599:
"The disk filter in
0.142.0disablestarget=log, but dependency library logs are filtered by their original dependency target before tracing-log relabels them astarget=log, so they slipped through—and high-frequency dependency TRACE kept going into SQLite."
This complete fix is not in any stable release; it only landed in 0.143.0-alpha, released on 2026-06-24. As of this verification, both #28224 and #17320 are still Open. So the accurate version is: upstream has reduced the noise, and the full fix is on the way, but upgrading to the current latest stable version does not count as fixed yet.
③Is it true or false?
Point by point:
| Claim | Measurement | Verdict |
|---|---|---|
Secretly writes to logs_2.sqlite |
Really exists, 126 MB | True ✓ |
| Writes "tens of thousands" of junk entries | An underestimate. 8.45 million rows inserted in 10 days, 66% of them websocket TRACE | True (and more than that) ✓ |
| It writes even if you do nothing | Basically true. I did not open the CLI, but the background desktop app-server was still writing; as long as a Codex process is running, websocket polling may keep emitting TRACE | True ✓ |
| The file does not look larger, but underneath it is writing like crazy | That is exactly the pathology. Rolling deletion keeps the size stable, but physical writes do not stop | True ✓ |
| It will burn through your SSD in a year | Depends on usage. Heavy users should care; light users do not need to panic | Exaggerated ≈ |
Where the root cause is (source code dug up by lencx)
When app-server and the TUI start, both attach a SQLite logging layer, whose default filter is:
.with_filter(Targets::new().with_default(Level::TRACE))
In other words, this disk sink accepts TRACE by default and has its own filter chain, so it is not constrained by RUST_LOG=warn. The reporter of #17320 had already set RUST_LOG=warn, but TRACE entries still kept appearing in logs_2.sqlite. That issue was opened on 2026-04-10 and was still Open when checked.
SSD risk depends on write volume
An SSD's TBW is the warrantied write amount, not a death line where the drive fails as soon as you cross it. Risk depends on usage intensity, drive capacity, and free space:
- Heavy users: If you are at 640 TB/year like #28224, and that lands on a soldered 256 GB Mac SSD (rated around 150 TBW), you can exceed the warrantied write amount in a few months. That is worth addressing.
- Light users: If write volume is one or two orders of magnitude lower, the drive can still last many years, so there is no need to panic.
There is another misconception: it will not "burn through the same tiny spot." SSDs have FTL and wear leveling; writing to the same filename does not mean repeatedly erasing the same flash cells. What you should really look at is total host writes, plus write amplification when the disk is too full.
There is another easily overlooked privacy risk: lencx points out that several issues mention this library may write raw websocket/SSE payloads to disk, which means conversation fragments may be mixed in. So do not treat logs_2.sqlite as a "harmless cache" and casually upload, back up, or publish it.
My conclusion: the mechanism is real, upstream evidence exists, and I can still reproduce it locally; but it is not "everyone burns through their SSD in a year." It depends on which Codex processes you keep running, how long you use it each day, and your machine's SSD capacity and free space. My first version only looked at residual row counts and judged it as FUD; that was wrong.
④What to do
Most of the following comes from lencx's postmortem, and I verified it on my machine. Fully quit Codex before taking action, otherwise you may hit the file-handle problem from #22444: the process is still holding the deleted WAL, so du looks lower, but df does not release the space.
First confirm no process is holding it
pgrep -fl 'Codex|codex|app-server'
lsof -nP | grep -E '\.codex/.+logs_2\.sqlite'
Clean up and reclaim space (symptom treatment)
# After confirming Codex has exited
sqlite3 ~/.codex/logs_2.sqlite 'DELETE FROM logs; VACUUM;'
This can shrink the file from 126 MB back to a few MB, but it cannot stop it from starting to write again. Cleanup ≠ rate limiting.
Temporary stopgap: block new writes (symptom treatment, but effective)
sqlite3 ~/.codex/logs_2.sqlite \
"CREATE TRIGGER IF NOT EXISTS block_log_inserts
BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"
# Roll back
sqlite3 ~/.codex/logs_2.sqlite "DROP TRIGGER IF EXISTS block_log_inserts;"
The trigger ignores all inserts, and the effect is immediate. The tradeoff is that feedback_log_body becomes empty, so bug reports will lack diagnostic attachments; if an upgrade recreates the table, the trigger may be deleted, so recheck after upgrading.
Move the database to another disk (workaround)
# ~/.codex/config.toml
sqlite_home = "/Volumes/ExternalDisk/codex-sqlite"
# Or temporarily: CODEX_SQLITE_HOME=/Volumes/ExternalDisk/codex-sqlite codex
This is cleaner than symlinking a single file, but it only moves the writes elsewhere; it does not reduce them. If the external disk is disconnected or sleeps, Codex will be affected. Note that it migrates the entire SQLite home, so state, logs, goals, and memories will all move with it.
Three traps to avoid
/tmpsymlinks are useless on macOS: macOS points/tmpto/private/tmp, which is on the APFS system disk, not a RAM disk. Symlinking there merely moves it from one SSD directory to another, doing nothing to reduce writes. Verify it yourself withreadlink /tmp; mount | grep -i tmpfs.RUST_LOG=warncannot control this sink: it can quiet stderr, but this SQLite persistence layer has its own filtering chain (see #17320).[feedback] enabled = falseis not a switch: it only disables the feedback submission flow; the logging layer is still attached at startup.
While you're at it: exclude WAL/SHM from backups
--exclude='*.sqlite-wal'
--exclude='*.sqlite-shm'
WAL and SHM are runtime files; syncing tens of GB to another machine is usually just copying the problem.
Upstream still needs to tighten this further
All of these are only stopgaps. Codex still needs to narrow the default level of the disk sink further, avoid writing raw websocket payloads by default, cap WAL and total size, and provide an explicit switch (such as sqlite_log_level). #28224 has been closed due to a noise-reduction PR, but #17320, #22444, #24275, and #28997 were still Open when checked.
Take one minute to decide whether you need to care: first run ls -lh ~/.codex/logs_2.sqlite* to check the size, then run SELECT max(id),count(*) FROM logs once. If max(id) is in the tens or hundreds of millions while the row count is only a few hundred thousand, your machine is also "writing, deleting, then writing again." If you use it heavily, especially on a Mac with a soldered SSD, it is worth addressing; if you only use it occasionally, just knowing about it is enough.

WeChat Pay
Alipay
Comments
Replies are public immediately and may be moderated for policy violations.