Excessive SSD write churn from `scope_v3.json` and `models_cache.json`
What version of the Codex App are you using (From “About Codex” dialog)?
26.707.41301
What subscription do you have?
Pro
What platform is your computer?
Darwin 25.5.0 arm64 arm
What issue are you seeing?
Summary
Codex/ChatGPT desktop appears to be repeatedly rewriting two very small JSON files, causing multiple GB/day of SSD writes:
~/Library/Application Support/Codex/sentry/scope_v3.json~/.codex/models_cache.json
Over the last 7 days, these two files alone accounted for about 24.9 GB of path-attributed local NVMe writes, despite being only ~100 KB and ~270 KB on disk.
Environment
- App: ChatGPT / Codex desktop
- Bundle identifier:
com.openai.codex - App version:
26.707.41301 - Bundle version:
5103 - macOS:
26.5.2, build25F84 - Architecture:
arm64
Evidence
Measured using a persistent fs_usage -w -f filesys monitor, filtered to local NVMe writes.
Window:
2026-07-04 19:36 EDT -> 2026-07-11 19:36 EDT
Observed writes:
17.57 GB/week 2.51 GB/day 110,288 writes
~/Library/Application Support/Codex/sentry/scope_v3.json
Current file size: ~100.8 KB
Approx write amplification vs current size: ~170,000x
7.33 GB/week 1.05 GB/day 27,357 writes
~/.codex/models_cache.json
Current file size: ~270.1 KB
Approx write amplification vs current size: ~26,500x
Together:
24.90 GB/week
3.56 GB/day
These are not large growing files. They are tiny JSON files that appear to be rewritten frequently.
What these files appear to contain
scope_v3.json is Sentry diagnostic/error-reporting state. Locally it has top-level keys:
scope
event
models_cache.json appears to be the model list cache. Locally it has keys:
fetched_at
etag
client_version
models
Expected behavior
These files should only be rewritten when their contents materially change.
Examples:
models_cache.jsonshould only change when model metadata is refreshed and differs, or on a reasonable cache interval.scope_v3.jsonshould not continuously rewrite the full Sentry scope/event JSON during normal use or idle/background operation.
Actual behavior
Both files are rewritten frequently enough to generate GB/day of SSD writes:
scope_v3.json: ~2.51 GB/day from a ~101 KB filemodels_cache.json: ~1.05 GB/day from a ~270 KB file
This looks like whole-file rewrite churn rather than necessary append/growth behavior.
Impact
This creates unnecessary SSD wear. On my machine, overall SSD writes have been a concern, and these two tiny Codex files account for a meaningful chunk of daily writes by themselves.
Suggested fixes
- Avoid rewriting JSON files when serialized contents have not changed.
- Add debounce/throttling around Sentry scope persistence.
- For
models_cache.json, honoretag/cache freshness and skip writes when the fetched model list is identical. - Prefer atomic write only after comparing old/new content hash.
- Consider moving volatile diagnostic state to memory or a less write-heavy persistence strategy.
Notes
I am not including the raw file contents because they may contain diagnostic/session metadata, but I can provide sanitized structure or additional fs_usage excerpts if useful.
What steps can reproduce the bug?
I do not have a minimal source-code reproducer, but this is reproducible by running the macOS ChatGPT/Codex desktop app during normal Codex usage while monitoring filesystem writes.
Reproduction steps
- Install/run ChatGPT/Codex desktop on macOS.
- Use Codex normally for interactive coding sessions over several hours/days.
- In parallel, monitor local NVMe filesystem writes with
fs_usage.
Example monitor command:
sudo fs_usage -w -f filesys | grep -E 'scope_v3\.json|models_cache\.json'
For longer-term measurement, I ran a persistent fs_usage -w -f filesys monitor and aggregated local NVMe write bytes by process/path.
6 Comments
?!?!?!?!?!?!?
OpenAI gives no response to #28224 after Jun 23 until Jul 9, continuing to burn up customer SSD drives, adding PRs #31789 #31792 #31791 and #31790 which may fix the issue but aren't in a release yet.
Now this?
After the embarrassment of #28224, how is there not a simple regression test that makes sure that Codex while idle isn't making unnecessary I/O? And another that makes sure when Codex is writing that its I/O doesn't absurdly change between releases?
Perhaps no one should run coding agents locally. Everything should be on the cloud, and let the cloud deal with SSD/heat/power and the waste.
Both write streams are source-pinnable, and they're two independent mechanisms — one in this repo's Rust core, one in the bundled
@sentry/electronSDK. Static analysis only (no macOS here to run the desktop app), but the code paths match yourfs_usagenumbers closely. Note the four PRs that closed #28224 (#31789–#31792) are all SQLite trace-volume fixes and don't touch either of these files, so this is a separate, still-open churn class.models_cache.json— every model API response rewrites the full catalog to bump one timestampThe chain, at main
9e552e9d15ba:ModelsEtagevent from the response headers — once per HTTP response (sse/responses.rsL78-80), same on the websocket path (responses_websocket.rsL282-284).refresh_if_new_etag(core/src/session/turn.rsL2271-2277).renew_cache_ttl()(manager.rsL319-333).renew_cache_ttl()reads the whole file, updates onlyfetched_at, and rewrites the entire pretty-printed catalog viafs::write(cache.rsL95-102 →save_internalL116-123).So the write cadence is exactly "one full ~270 KB rewrite (plus a full read) per model API response." In agentic use every tool-call round trip is a response, so thousands of writes per day is normal operation. Your measurement — 27,357 writes/week ≈ 3,900/day, and 27,357 × 270 KB ≈ 7.4 GB ≈ your observed 7.33 GB/week — is consistent with per-response renewal, with nothing else needed to explain it.
The irony is that the renewal exists to keep a 300-second TTL fresh (
manager.rsL27), so at most ~1 write per 300 s is ever useful; everything above that frequency changes nothing but the timestamp.Minimal fix outline (no behavior change):
renew_cache_ttl(), skip the save when the cache is still young, e.g.if now - cache.fetched_at < ttl/2 { return Ok(()) }. That caps writes at ~1 per 150 s per process (≤ ~155 MB/day worst case vs. the observed 1.05 GB/day) while fully preserving the cross-process freshness the renewal exists for.save_internal(), compare serialized bytes against the existing file and skip identical writes — this also coverspersist_cache()re-writing an unchanged catalog after anOnlinefetch.fs::writeis truncate-then-write, not atomic. Two concurrent codex processes sharing~/.codexcan tear the file; a reader hitting a partial file gets a serde error and treats the cache as absent (extra refetch). Temp-file + rename would close that too.scope_v3.json— sentry-electron persists the merged scope on every breadcrumbThis one lives in the bundled SDK, not this repo, but the mechanism is visible in
@sentry/electron(master24e888207dcc; the on-disk filenamescope_v3matches):sentryMinidumpIntegrationregisters a listener on all three scopes (isolation/current/global —common/scope.tsL16-29) so JS context can be attached to native crash minidumps.sentry-minidump/index.tsL60-76, store created at L220).BufferedWriteStorethrottles to one trailing-edge write per 500 ms, but writes unconditionally — there is no content comparison, so an unchanged scope still gets rewritten (store.ts—BufferedWriteStore.setoverStore.set's full-filewriteFile).At the 500 ms throttle ceiling that's up to 2 × ~101 KB/s while the app is busy; your 110,288 writes/week (~15,700/day ≈ 2.2 h/day at the throttle ceiling, or a lower sustained rate) is well within what routine breadcrumb traffic produces. With the default
maxBreadcrumbsof 100, the persisted blob stays near its max size on every write.Knobs available to the Codex team (app config, since the desktop app is where this integration runs): lower
maxBreadcrumbs, or drop the minidump scope-persistence integration if native-crash context isn't essential. Upstream-fixable in the SDK: compare serialized content before writing (the pending-write structure already holds the last payload), lengthen the throttle, and stop re-serializing the near-staticeventdefaults on every scope tick.Scope of this analysis
Pinned by reading the open-source
codex-rscore (which the desktop app bundles for~/.codexstate) and the current@sentry/electronsource; I can't run the macOS desktop app, so the exact SDK version bundled in26.707.41301and the scope_v3 write split are inferred from yourfs_usagedata rather than reproduced live. Happy to expand any step of either chain.Independent reproduction on a newer Desktop build: ChatGPT/Codex 26.721.41059 (build 5848), bundled
codex-cli 0.146.0-alpha.3.1, macOS 27.0 arm64.During a 30-second stat-and-SHA-256 observation window in an active Codex session:
scope_v3.jsonwas rewritten 10 times after the initial observation~/.codex/models_cache.jsonwas rewritten onceThis was a read-only short-window observation, not a path-attributed physical-I/O measurement, so I am not extrapolating it to a daily write figure. It does confirm that frequent whole-file
scope_v3.jsonrewriting persists in the current 26.721.41059 Desktop build.i left my laptop overnight working on a simple dotnet project, and using CrystalDIskInto, it wrote total f 140GB withing 6 hours span
using FileActivityWatch, it reported consistent 700MB writes to these files along the night
<img width="370" height="143" alt="Image" src="https://github.com/user-attachments/assets/bf651f73-01e2-49b3-bbb0-eaef09275ecc" />
Two days of monitoring and 92Gb of written data to scope_v3.json. That's crazy. The problem appeared in the previous release: Powered by Codex & OWL Version 26.810.41047
Marking scope_v3.json file as read-only prevent all writes.