Codex session logs grow to 700MB-2GB from repeated compaction history and raw tool output
What version of Codex CLI is running?
0.118.0
What subscription do you have?
Pro
Which model were you using?
gpt-5.5
What platform is your computer?
Darwin 24.6.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
codex-tui
Codex doctor report
What issue are you seeing?
Codex CLI/TUI is creating extremely large session JSONL files under ~/.codex/sessions.
One affected session file is 732 MB:
~/.codex/sessions/2026/03/31/rollout-2026-03-31T22-18-46-019d458c-752d-76c0-bbfa-a29eafa0cbf0.jsonl
A quick breakdown shows the size is dominated by repeated compaction history and raw tool outputs:
- 337.8 MB from
compactedrecords - 230.8 MB from
function_call_output - 54.8 MB from
reasoning - 26.4 MB from
token_countevents - 18.0 MB from
turn_context
The file has 170,415 JSONL records. The largest single record is about 2.1 MB. My ~/.codex/sessions directory is now about 91 GB total, with 184 JSONL files over 100 MB
and the largest observed file around 1.95 GB.
This looks like unbounded session logging / repeated persistence of compaction history rather than expected transcript storage.
What steps can reproduce the bug?
- Use Codex CLI/TUI for a long coding session with multiple tool calls.
- Run broad search commands that can produce large outputs, for example
rgacross large repos or/tmp. - Continue the session long enough for context compaction to occur multiple times.
- Inspect
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl. - Check file size and record composition with commands like
du -h,wc -l, andjq.
Observed result:
The session JSONL grows to hundreds of MB or more. In one case, a single rollout file reached 732 MB with 170,415 records.
The biggest contributors were:
- repeated
compactedrecords containing largereplacement_historypayloads - raw
function_call_outputpayloads - reasoning and token count event records
What is the expected behavior?
Session logs should stay bounded and should not grow to hundreds of MB or multiple GB for a normal long coding session.
Expected behavior:
- Tool outputs should be truncated, summarized, deduplicated, or stored with a size cap.
- Context compaction should not repeatedly persist large prior history snapshots in
replacement_history. - Old session logs should be compressed or covered by a retention policy.
- A long tool-heavy session might produce a larger log, but it should remain within a reasonable size, not 700MB-2GB per JSONL file.
Additional information
Affected file:
~/.codex/sessions/2026/03/31/rollout-2026-03-31T22-18-46-019d458c-752d-76c0-bbfa-a29eafa0cbf0.jsonl
File stats:
- Size: 732 MB
- Lines: 170,415
- Largest single JSONL record: ~2.1 MB
- Average record size: ~4.5 KB
Breakdown by record type:
compacted: 337.8 MB across 684 recordsresponse_item/function_call_output: 230.8 MB across 35,368 recordsresponse_item/reasoning: 54.8 MB across 23,659 recordsevent_msg/token_count: 26.4 MB across 38,885 recordsturn_context: 18.0 MB across 955 recordsresponse_item/custom_tool_call: 16.3 MBresponse_item/function_call: 14.5 MB
Two large rg tool outputs in the affected session:
- ~1.9 MB output from an
rgcommand across local repos - ~1.3 MB output from an
rgcommand across/tmpand a local repo
Broader local impact:
~/.codex/sessionstotal size: ~91 GB- JSONL files over 100 MB: 184
- Largest observed session JSONL: ~1.95 GB
Session metadata:
originator: codex-tui- affected session
cli_version: 0.118.0 - current installed CLI:
codex-cli 0.134.0 - current binary path:
/opt/homebrew/bin/codex - affected session records
model_provider: openai, but no concrete model ID is present
Please try reproducing with the affected CLI version 0.118.0 by downloading/installing that version, running a long tool-heavy Codex TUI session, and then inspecting the
generated ~/.codex/sessions/.../rollout-*.jsonl file size and record breakdown.
16 Comments
We've decided to eliminate the TUI logs entirely. They were largely redundant because we now log to a sqlite db, and those logs are properly rotated and pruned, whereas the TUI logs were append-only. This change will be in the next release.
i am not sure why this closed while its still an issue. The TUI also causing the issue. my codex sessions are in GB now.
Reopening because I misinterpreted the original bug report. I thought you were referring to logs (as indicated in the title), but upon closer read, you were talking about session rollout files. These rollout files are designed to record the full session details, so this isn't a bug. It's unusual for sessions to be so large that they consume hundreds of MB, but this is certainly possible if you use the same session repeatedly or use
/goalto drive the agent to work for long periods of time.We are looking to add compression to reduce the disk footprint. We're also going to add a delete command if you want to remove specific session rollouts completely.
Adding a cross-link because this issue covers large rollout JSONL growth generally, and the more specific mechanism is tracked in #23257.
We inspected an affected local rollout that reached about
11.36 GiB. The detailed sanitized breakdown is here: https://github.com/openai/codex/issues/23257#issuecomment-4767499090Short version:
287compacted records contributed about10.68 GiBof the11.36 GiBrollout.payload.replacement_historycontaining response items.52.1 MiB.43uniqueinput_imagepayloads totaling about51.3 MiBwere copied as9,173image parts across compaction snapshots.input_image.image_urlcopies accounted for about10.66 GiBon disk, roughly~213xamplification of the unique image payload footprint.So this is not only "long session produces a large transcript". At least for this sample, append-only rollout persistence plus full
replacement_historysnapshots turns retained inline image payloads into runaway disk growth.The latest version of the CLI (0.141.0) includes a
codex delete <session ID>command that you can use if you want to delete specific sessions that have grown really large. I realize that's only part of the solution here, but I wanted to mention it in case it's of help to anyone who wants to free up some disk space.note that you can also just ask codex to salvage these sessions. my 11.36gb session is now 700mb with all the images moved to the last compaction row.
@banteg I've done that, but with long-running /goal sessions, the session data keeps accumulating and both the JSONL logs and SQLite db continue to grow over time. I just have a cron job which prunes every few days..
Following up with data specific to the /goal angle @etraut-openai flagged back in June ("if you use... /goal to drive the agent to work for long periods of time").
Makes sense since I have 14GB of Codex session history since using GPT Pro 20x 5.6 Sol (X-high/Ultra) in Goal Mode; that means in the last 2 days I had 14 GB of memory eaten (yes, this was token-heavy work, I used 4 resets and ~1.5B tokens in one day alone).
<img width="768" height="173" alt="Image" src="https://github.com/user-attachments/assets/6eb074c6-7070-468a-a45b-8466947f49fa" />
and temporarily solved with a symlink to an external drive
<img width="747" height="495" alt="Image" src="https://github.com/user-attachments/assets/85f014e4-1789-44b5-b7b8-780e1b246cce" />
Additional sanitized reproduction from macOS 26.4 (25E246), arm64.
Current installed binary is
codex-cli 0.144.5. Most of the affected rollout files were authored bycodex-tui 0.144.0-alpha.4, with smaller amounts from 0.144.0 through 0.145.0-alpha.16.Snapshot of
~/.codex/sessions:0.144.0-alpha.4alone account for about 11.7 GiBA representative subagent rollout is 101,184,191 bytes (96.5 MiB). Serialized-record breakdown:
Its session metadata identifies the source as
source.subagent.thread_spawn. This suggests an additional amplification path beyond a single long-running root thread: subagent rollouts inherit substantial parent context, then repeatedly persist that context incompacted.payload.replacement_history. In this sample, compacted records alone are roughly 80% of the file.No inline images dominate this particular sample. The dominant data is repeated compaction history. This remains observable in rollout files from the 0.144.x line, not only the 0.118.0 version in the original report.
I investigated this on a real, privacy-sensitive workload and have a small, tested compression patch ready. I am posting the analysis first, per the invitation-only contribution policy, and would appreciate maintainer alignment/invitation before opening a PR.
Impact
On Codex 0.144.2,
~/.codex/sessionsreached 75,412,910,173 bytes (70.23 GiB) across 644 rollouts after roughly one to two months of heavy use:The audit is content-private: it reports sizes, JSON paths, counts, and hashes, and never prints message/tool contents.
What is being duplicated
I recursively analyzed a representative 760,247,640-byte (725 MiB) child rollout:
compactedrecords: 652.06 MiB / 89.936% of the file.data:URLs: 659.78 MiB.payload.replacement_history[].content[].image_url: 632.39 MiB.Exact-line deduplication would save only about 1.10 MiB because timestamps and surrounding metadata differ. This is content/history-level duplication, consistent with #23257.
There is also substantial parent/child duplication. Comparing strings of at least 256 bytes by SHA-256, the child contained 705.39 MiB of large strings and 704.50 MiB already existed in its parent: 99.874% overlap by bytes (98.434% by unique values).
Source trace
At current main (
56395bddaf26eb2829387ca6a417bf9128e5b239when tested):session::replace_compacted_historypersistsreplacement_history: Some(items.clone()); local/remote/v2 compaction paths all create full replacement histories.LiveThread::create_with_inherited_model_context;SessionMeta.history_baseexists, but the observed legacy rollouts still physically contain inherited context and the common creation/extraction paths leave it unset..jsonl.zstreading/materialization and a cold-compression worker already exist inrollout/src/compression.rs, butLocalThreadStoreCompressionis under-development/default-off, waits seven days, and uses zstd level 3 with its default match window.The current compression infrastructure is a good base, but default zstd cannot see repeated blocks separated by tens/hundreds of MiB. Also, a fixed seven-day delay does not help a workload that generated more than 50 GiB in two days.
Real-file compression benchmark
All rows use the same 760,247,640-byte rollout; full round trips were byte-identical by SHA-256.
| Encoding | Compressed bytes | Ratio | Representative resource result |
|---|---:|---:|---|
| gzip -6 | 498.3 MB | 1.53x | 20.62 s |
| zstd -3 default | 463,794,252 | 1.64x | 0.43 s, about 101.8 MiB RSS |
| zstd -3, long=23 | 108,474,356 | 7.01x | |
| zstd -3, long=24 | 72,554,649 | 10.48x | |
| zstd -3, long=25 | 52,901,838 | 14.37x | compress about 0.32 s / 58.3 MiB RSS; decompress about 0.20 s / 37.9 MiB RSS |
| zstd -3, long=27 | 32,856,569 | 23.14x | compress about 0.34 s / 166 MiB RSS; decompress about 0.19 s / 138.8 MiB RSS |
I selected a 32 MiB maximum window (windowLog 25) rather than the best ratio. It gives most of the storage benefit while keeping encoder/decoder memory far below long=27 and is appropriate for two background jobs.
Focused patch ready
The local topic branch changes only
codex-rollout(55 inserted lines across two files):windowLog = ceil(log2(source_size)), clamped to 23..25;Validation:
just test -p codex-rollout: 100/100 passed;just fix -p codex-rollout: passed;just fmt: passed;git diff --check: clean.I also verified the installed 0.144.2 app-server can
thread/readboth a synthetic and a real compressed rollout without materializing it. A guarded local migration compressed 194 closed rollouts, verified every frame and decompressed SHA-256, skipped six files observed open, and explicitly saved 53.78 GiB. The session directory fell from 70.23 GiB to about 19.3 GiB while preserving logical JSONL bytes. These were cold candidates and no integrity failures were observed, butlsofdouble checks cannot theoretically close an open-after-check race. A general migration should stop Codex writers, and the automatic worker should resolve the representation/append coordination called out in #25089 before default enablement.Release compatibility follow-up
I downloaded official macOS arm64 release binaries, verified each asset against the SHA-256 digest in GitHub Release metadata, and ran the same isolated app-server probe through both
thread/readandthread/resume:| Stable release | Read
.jsonl.zst| Resume/materialize plain JSONL ||---|---|---|
| 0.135.0 | No; treats the frame as ordinary UTF-8 JSONL | No |
| 0.136.0 | No; treats the frame as ordinary UTF-8 JSONL | No |
| 0.137.0 | Yes | Yes |
| 0.141.0 | Yes | Yes |
| 0.144.2 | Yes | Yes |
| 0.144.6 | Yes | Yes |
So 0.137.0 is the earliest tested downgrade-compatible stable release; merge/release timing alone would have incorrectly suggested 0.136.0. The local migration tool now requires an actual supplied Codex binary and refuses to modify storage unless its isolated read-and-resume probe passes.
Proposed sequencing
Would this focused compression change align with the maintainers' intended direction? If so, I would be happy to submit and own the PR; please explicitly invite it per
docs/contributing.md. I can also provide the privacy-preserving audit tool and more benchmark details if useful.Follow-up: I finished the writer/compressor coordination work and the default-activation experiments locally. I have kept them as separate, atomic commits so the representation safety fix does not depend on the policy decision.
The open-handle race is reproducible
The existing before/after metadata checks do not cover this ordering:
I added a deterministic regression for exactly that sequence and implemented a schema-neutral coordination patch:
JsonlWriterlifetime;same-fileidentity and retry representation resolution if the inode changed;spawn_blocking;create(true), so a missing/deleted session cannot be accidentally resurrected.The regression now proves that the active recorder remains plain, an append after the worker scan succeeds, and compression after shutdown reloads all three items with zero parse errors. The stacked rollout suite is 103/103 passing.
cargo check, the Bazel rollout unit target, Bazel lock checks,just fix, and formatting pass. A Rust 1.95 Windows MSVC minimal compile probe coveringsame-file::HandleandFilelocks also passes; the full Windows cross-check stops earlier in third-partyaws-lc-sys/ringbecause this macOS host lacks MSVC SDK headers.A seven-day, startup-only policy is too late
I also recomputed the current inventory using plain sizes plus the decompressed size recorded in existing zstd frames, so the earlier local migration does not hide the logical workload: 827 rollouts represent 79.67 GiB of JSONL.
I prepared a follow-up worker-policy commit that runs at startup and every 30 minutes, compresses only files >=8 MiB and >=30 minutes old, and relies on the shared/exclusive file lock to skip active writers. It distinguishes a six-hour in-progress lease from a 30-minute completed-run cooldown, so a slow worker is not mistaken for a stale one.
skipped_too_smallis separately observable.An isolated app-server built from the stack, with no feature override, automatically compressed an old 9,437,746-byte synthetic rollout, skipped an old small rollout and a fresh large rollout, and read the compressed thread successfully. An explicit
local_thread_store_compression = falsekept the large rollout plain. An unwritable-destination fault injection preserved the source JSONL and created no compressed sibling. The synthetic fixture is only a lifecycle test; the real compression-ratio evidence remains the 725 MiB benchmark from the previous comment.Compatibility boundary and proposed PR order
The file lock is cooperative. It protects new writers/compressors, but an old Codex writer does not hold the shared lock; Unix advisory locks cannot force such a process to participate. I therefore kept the two-line stable/default-on change separate and would like maintainer guidance on the support policy for mixed old/new binaries concurrently writing one
CODEX_HOME.Proposed review order:
If this split aligns with the intended direction, please explicitly invite the PR(s) per
docs/contributing.md; I am ready to submit and own the review/CI follow-through.Independent reproduction + tested fix ready
What users would experience
.jsonlfiles.This keeps the current logical rollout format and Codex read/archive/unarchive/resume behavior. It does not introduce content-addressed references or another on-disk schema in the first stage.
Measured result
| Check | Result |
|---|---:|
| Initial real user directory | 70.23 GiB physical after about 1–2 months |
| Complete product-path shadow | 79.613474 GiB logical -> 7.849757 GiB physical (10.1422x) |
| Preservation | 660/660 byte-exact; 0 missing, added, or mismatched |
| Eligible files | 156/156 compressed |
| One-time backlog worker | 24.399 s in the background |
| Foreground
thread/listp50/p95 | 23.871/26.386 ms during worker vs 24.258/26.007 ms afterward || Steady no-candidate pass | no rollout writes; 0.5 s; 3.172 MiB peak RSS |
| Largest current cold resume | 3.544552 GiB materialized byte-exact in 2.598 s at 37.438 MiB peak RSS |
The live sessions tree and source state DB were never modified: the full-store tests used APFS clonefile shadows and an online SQLite snapshot.
The user-impact numbers use strict units and semantics.
70.23 GiBis physical storage, equivalent to75.41 GB; it is bytes, so there is no divide-by-eight conversion. A separate mtime snapshot found four plain rollouts modified on 2026-07-19 totaling 5.34 GiB, including one 3.48 GiB file. Session path dates are start-date cohorts, not calendar-day net growth.<details>
<summary><strong>Maintainer / review-agent details</strong></summary>
Why the files grow
The local audit found large payloads repeated across compaction
replacement_historysnapshots and inherited parent history copied into many child/subagent rollouts. Exact-line deduplication helps little because surrounding JSON records differ. In the largest 379-child parent group, a privacy-safe exact-string audit found 63.206185 GiB of repeated strings at least 64 KiB inside 72.600540 GiB of logical JSONL; this structural result is not multiplied by the zstd ratio and remains a separate future format/lifecycle decision.Patch boundaries
This extends the existing upstream design rather than introducing a parallel storage architecture. The original #25089 worker PR explicitly left writer/compressor coordination unresolved and named a shared representation/append lock as a possible production-readiness requirement. The active-session follow-up #28338 received the same live-recorder race concern. The local coordination layer now provides a deterministic red-before-fix reproduction and closes both writer orderings.
The local series is deliberately staged:
The first patch is a schema-neutral 55-line codec change with no activation, reader, retention, or model-context behavior change. I can submit the series in whatever staged shape maintainers prefer.
The final change-size audit split recovery into 411-line and 159-line semantic commits, both below the repository's 500-line guidance for complex logic. The split activation and integration heads are Git-tree identical to the previously validated heads; this improves review and rollback boundaries without changing runtime behavior or invalidating the measurements below.
Safety findings closed before submission
InvalidDataand preserves both files.Validation and compatibility
codex-rollout115/115, 0 skipped;</details>
Would a maintainer please invite me to submit the staged fix, beginning with the focused codec patch if that is the preferred review boundary? I am ready to own the PR through review, CI, follow-up changes, merge, and release validation.
@jif-oai — you authored the current rollout compression/materialization path (#25087, #25089, #28338), so I’m asking you specifically to confirm the first review boundary.
I have a two-file, 55-line codec-only PR ready: bounded zstd long-distance matching (8–32 MiB window) plus frame checksums, with no activation, reader, schema, retention, or model-context changes. On a real 725 MiB rollout it improved compression from 1.64x to 14.37x, byte-exact, and the independent
codex-rolloutsuite passes 100/100.If that scope aligns with the intended direction, would you please explicitly invite me to submit it per
docs/contributing.md? I’ll own review, CI, follow-up changes, and release validation. The larger writer-coordination and policy work described above will remain separate.@jif-oai !image
This fix is badly needed, I have a 1.5 TB ~/.codex/sessions folder, and growing, and had to symlink to a slow external drive. Horrible UX. I also can't just "throw away" the June, May, April etc. folders: The file system shows they were touched / written to so I am afraid to corrupt the JSONL tree beyond all repair.
As a small band-aid, Mac users can buy themselves some time with the
applesauceHomebrew tool, its use is straightforward:applesauce compress "~/.codex/sessions"Ironically, Apple's file system compression (which is roughly the equivalent of the well-known NTFS in-place file compression), stops at 4 GB, so exactly the files that need it most, won't get compressed. 🤦♂️