Excessive SSD write churn from `scope_v3.json` and `models_cache.json`

Open 💬 6 comments Opened Jul 11, 2026 by W944
💡 Likely answer: A maintainer (darlingm, contributor) responded on this thread — see the highlighted reply below.

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, build 25F84
  • 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.json should only change when model metadata is refreshed and differs, or on a reasonable cache interval.
  • scope_v3.json should 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 file
  • models_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, honor etag/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

  1. Install/run ChatGPT/Codex desktop on macOS.
  2. Use Codex normally for interactive coding sessions over several hours/days.
  3. 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.

View original on GitHub ↗

6 Comments

darlingm contributor · 1 month ago

?!?!?!?!?!?!?

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?

we-taper · 1 month ago

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.

Echolonius · 1 month ago

Both write streams are source-pinnable, and they're two independent mechanisms — one in this repo's Rust core, one in the bundled @sentry/electron SDK. Static analysis only (no macOS here to run the desktop app), but the code paths match your fs_usage numbers 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 timestamp

The chain, at main 9e552e9d15ba:

  1. Every completed Responses stream emits a ModelsEtag event from the response headers — once per HTTP response (sse/responses.rs L78-80), same on the websocket path (responses_websocket.rs L282-284).
  2. The turn loop forwards it to refresh_if_new_etag (core/src/session/turn.rs L2271-2277).
  3. In the common case — ETag unchanged — it calls renew_cache_ttl() (manager.rs L319-333).
  4. renew_cache_ttl() reads the whole file, updates only fetched_at, and rewrites the entire pretty-printed catalog via fs::write (cache.rs L95-102 → save_internal L116-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.rs L27), 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):

  • In 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.
  • In save_internal(), compare serialized bytes against the existing file and skip identical writes — this also covers persist_cache() re-writing an unchanged catalog after an Online fetch.
  • Secondary: fs::write is truncate-then-write, not atomic. Two concurrent codex processes sharing ~/.codex can 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 breadcrumb

This one lives in the bundled SDK, not this repo, but the mechanism is visible in @sentry/electron (master 24e888207dcc; the on-disk filename scope_v3 matches):

  1. sentryMinidumpIntegration registers a listener on all three scopes (isolation/current/global — common/scope.ts L16-29) so JS context can be attached to native crash minidumps.
  2. Every scope mutation — each breadcrumb, tag, or context update, including breadcrumbs synced over IPC from renderer processes — serializes the full merged scope plus regenerated event defaults and hands it to the store (sentry-minidump/index.ts L60-76, store created at L220).
  3. BufferedWriteStore throttles 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.set over Store.set's full-file writeFile).

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 maxBreadcrumbs of 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-static event defaults on every scope tick.

Scope of this analysis

Pinned by reading the open-source codex-rs core (which the desktop app bundles for ~/.codex state) and the current @sentry/electron source; I can't run the macOS desktop app, so the exact SDK version bundled in 26.707.41301 and the scope_v3 write split are inferred from your fs_usage data rather than reproduced live. Happy to expand any step of either chain.

anotherpanacea-eng · 1 month ago

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.json was rewritten 10 times after the initial observation
  • its size varied from approximately 198 KB to 214 KB
  • its content hash changed on every observed rewrite
  • ~/.codex/models_cache.json was rewritten once
  • the 8.9 MB remote plugin catalog cache was not rewritten during this window

This 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.json rewriting persists in the current 26.721.41059 Desktop build.

CyberBonk · 14 days ago

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

crea7or · 13 days ago

<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.