Repository navigation
macOS: compaction checkpoints re-embed screenshots until rollout hits 3.9 GB; ingestion then uses 10–20 GB RSS, causing jetsam kills and a kernel watchdog panic on a 16 GB Mac #31040
Description
What version of the Codex App are you using?
- Codex Desktop (macOS):
26.623.81905(build 4598) - codex-cli:
0.142.5 - macOS 26.5.1 (25F80), MacBook Air (Mac16,12, Apple Silicon, 16 GB RAM)
What platform is your computer?
macOS (Apple Silicon).
What issue are you seeing?
The root cause appears to be the same as #23257 (compaction checkpoints repeatedly re-embed full inline image payloads), but on macOS the consequence escalates beyond bloated files to system-wide crashes: jetsam kills of system daemons and eventually a kernel watchdog panic.
1. Rollout files snowball on disk
One long browser-automation session produced a single rollout JSONL of 3.9 GB:
- 29,406 records total; 98
compactedrecords account for 3.99 GB of the file compactedsnapshot sizes grow monotonically: 0.1 MB → 3.4 MB → … → 53.4 MB each by the end of the session- A sampled 42.8 MB
compactedrecord contains 81data:imagebase64 payloads (the same screenshots re-embedded on every compaction) - Other sessions from the same week: 973 MB, 544 MB, 394 MB, 254 MB, 231 MB;
~/.codextotaled 10 GB (logs_2.sqlitealone 721 MB)
2. Session ingestion then uses 10–20 GB RSS
macOS resource-exceedance reports (Microstackshots) for the codex process show repeated memory explosions from a ~100 MB baseline while a sqlx/SQLite thread spins in sqlite3_step / sqlite3_wal_checkpoint_v2 / pwrite:
Footprint: 98.66 MB -> 6167.55 MB (+6068.89 MB) (max 12.19 GB)within a 7.5-minute sample windowFootprint: 104.02 MB -> 6774.80 MB (+6670.78 MB) (max 11.81 GB)(different pid, same day)- Five separate "disk writes" resource-exceedance reports over 3 days with the same stack signature (~10.5 MB written per sample step)
3. System impact on a 16 GB Mac
- A
JetsamEvent-*.ipsreport nameslargestProcess: codexat 15.5 GB RSS (lifetimeMax 20.5 GB) while macOS jettisoned dozens of system daemons - The following night: kernel panic —
watchdog timeout: no checkins from watchdogd in 90 seconds. The panic snapshot shows codex at 10.7 GB RSS, compressor 12.2 GB, 0.01 GB free - Collateral during the week: a WindowServer userspace watchdog timeout (forced session restart) and bursts of Chrome renderer crashes (abort traps) under memory pressure
What steps can reproduce the bug?
- On a 16 GB Mac, run a long Codex session that uses browser/screenshot tools heavily, long enough to cross many automatic compactions.
- Inspect the session rollout JSONL: each new
compactedrecord re-embeds prior screenshots as inline base64, so checkpoints grow roughly linearly and the file quadratically. - Leave the desktop app running: when codex (re)ingests the rollout into SQLite, RSS climbs to 10–20 GB → jetsam kills → possible watchdog kernel panic overnight.
What is the expected behavior?
compactedcheckpoints should deduplicate or reference image payloads instead of re-embedding them (as proposed in Desktop compaction repeatedly embeds full image base64 in compacted checkpoints #23257), and superseded checkpoints should be pruned.- Rollout/session ingestion should stream with bounded memory instead of holding multi-GB parsed history resident.
Related: #23257 (compaction re-embeds images, Windows), #24676 (desktop hangs opening image-heavy rollout), #24550.
Happy to attach sanitized excerpts of the resource reports, jetsam event, or panic log on request.
Activity
github-actions commentedon Jul 3, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
chyewon commentedon Jul 3, 2026
Follow-up: I root-caused this and have a validated patch on a fork branch. Since external PRs are invitation-only per CONTRIBUTING.md, posting the results here — happy to submit it as a PR if the team wants to invite it, or feel free to lift the branch directly.
Branch:
chyewon/codex:fix/rollout-memory-compacted-image-bloat(single commit,codex-rs/rolloutonly;cargo test -p codex-rolloutgreen, clippy clean)Root cause (confirmed in source + live instrumentation)
compactedrecords whosereplacement_historyre-embeds all prior inline screenshots (base64data:URLs inside kept user messages), so every checkpoint re-persists every historical screenshot — 98 checkpoints growing 0.1→53 MB each in my 3.9 GB rollout (mechanism of Desktop compaction repeatedly embeds full image base64 in compacted checkpoints #23257 / Codex session logs grow to 700MB-2GB from repeated compaction history and raw tool output #24948).load_rollout_itemscollects everything into aVec), and state-DB backfill visits every session file through it (extract_metadata_from_rollout). Measured on a real 973 MB production rollout: backfill peaks at ~1.0–1.6 GB;thread/resumevia app-server peaks at 4.8 GB (~5× file size — scaled to the 3.9 GB rollout that predicts ~19.6 GB, matching the jetsam/panic forensics above).The patch
data:images >16 KB from persisted compacted checkpoints (schema-valid 1×1 PNG placeholder; in-memory history untouched; small/remote images and non-compacted records unchanged) — stops the snowball at the source.for_each_rollout_itemvisitor — backfill memory becomes O(largest line) instead of O(file).from_value(v.clone())— removes a per-line full-DOM copy (transient-only win).Measured (same 973 MB production rollout)
state_db::init) peak RSS, same in-process harnessthread/resumepeak RSS on the redacted fileEnd-to-end checks on the redacted file:
thread/resumereturns the same thread id with zero parse errors, and a live model turn on the resumed session completed normally (provider accepted placeholder data URLs; the model replied).Includes a regression test (
recorder_redacts_large_inline_images_in_persisted_compacted_history) covering raw persisted bytes, placeholder round-trip throughload_rollout_items, and non-compacted records passing through unchanged.Update (same day): after an adversarial completeness review, the branch gained a second commit (
802e92a) extending the persist-time sanitizer beyond messageInputImagecontent to the other image-bearing shapes inreplacement_history:FunctionCallOutputcontent items (FunctionCallOutputContentItem::InputImage) andImageGenerationCall.result(bare base64), withcall_idpairing preserved. The regression test now also asserts the resume entry point (get_rollout_history) consumes a redacted rollout cleanly.cargo test -p codex-rolloutgreen, clippy clean. Known remaining limitation (intentionally out of scope): the resume path still eagerly materializes the full itemVec; with redaction in place files stop reaching GBs, but streaming resume reconstruction would be a worthwhile follow-up.loremcdmx commentedon Jul 26, 2026
Confirming this on macOS, at roughly 10× the scale reported in the opening post. Same mechanism, same log signatures.
Environment
ChatGPT.app26.721.41059, Codex Framework 150.0.7871.128Scale
~/.codextotal~/.codex/sessionslogs_2.sqlitestate_5.sqlitewas an additional 97 MB with a 4 MB WAL. The disk hit 94% full.Evidence that compaction re-embedding is the driver
After stripping image payloads, the 45 GB rollout is 79,899 lines. The 52,004 base64 PNGs it contained were packed into just 1,018 lines — about 51 images per line. That density is only explicable by snapshots re-embedding the same screenshots, exactly as described here.
Record breakdown of that file:
Head sample of the first 3,000 lines, by payload subtype:
Largest single JSON line: 2,274,751 bytes, a
function_call_outputwhoseoutput[0]is{"type":"input_image","image_url":"data:image/png;base64,..."}.Across the whole corpus, images were 1.6% of lines but 87.3% of bytes.
User-visible impact beyond disk
This is not only a storage problem — it degrades the app:
Opening the affected thread took 40–56 s. The app-server request queue sat pinned at
inFlightRequestCount=6with background requests expiring (reason=queue-expired), andturn/starttimed out at 30 s, surfacing as the "Error submitting message" banner (see #27395).Result of stripping images
Replacing every
data:image/png;base64,…payload with a 1×1 transparent PNG across 754 rollout files:That ratio suggests the actual conversational content of even a very heavy multi-month workload is on the order of a few GB — the rest is duplicated pixels.
Suggested fix
Store each image once in a content-addressed blob directory and reference it by hash in the rollout, rather than inlining base64. Failing that, excluding image payloads from compaction snapshots alone would remove the large majority of the growth, since the re-embedding is what turns linear growth into quadratic.
A checkpoint of
logs_2.sqlite/state_5.sqliteWALs on shutdown would also help; mine had 192 MB that never got merged.Workaround for anyone hitting this now
Rewriting each rollout line and substituting the base64 payload with a valid 1×1 transparent PNG keeps the JSON structure intact and the app reads the files normally. Non-image lines can be passed through byte-for-byte. Important: skip any file currently open by the app (
lsof -c codex -c ChatGPT -c Codex) — replacing an open file leaves the descriptor pointing at the old inode, so the space is never released.Related: #32128, #23257, #25815, #29531, #20864.