Multi-agent V2 full-history forks duplicate historical compaction snapshots and inline images, causing >100 GiB session storage growth
What issue are you seeing?
A long-running Codex Desktop conversation using Ultra reasoning and multi-agent V2 produced approximately 110 GiB of local session data under $CODEX_HOME/sessions.
The growth appears multiplicative rather than being caused by subagents producing unusually large outputs:
- Context compaction persists
Compactedrollout records containing a completereplacement_history. - These replacement histories can contain inline base64-encoded screenshots.
- Older compaction records remain in the append-only parent rollout.
- A full-history subagent fork loads the parent’s persisted rollout items.
- Those inherited items—including all historical compaction snapshots—are serialized into the child rollout.
- Ultra reasoning proactively creates many subagents, multiplying the retained history across many files.
Observed impact from one session tree:
| Measurement | Result |
|---|---:|
| Total rollout files | 300 |
| Total reported size | approximately 110.09 GiB |
| Subagent rollout files | 294 |
| Size attributed to subagent rollouts | approximately 106.56 GiB |
| Files sharing the same root session ID | 295 |
| Files at least 1 GB | 51 |
| Files at least 500 MB | 86 |
| Files at least 100 MB | 155 |
Of those files, 159 were recorded by 0.145.0-alpha.18 with multi_agent_version: "v2" and accounted for approximately 88.18 GiB.
Many individual subagent rollouts were between 1.3 GB and 1.9 GB.
A representative completed child rollout was 1,706,557,797 bytes and contained 23,564 JSONL records. Its composition was:
- 141
compactedrecords - Approximately 1.55 GiB in
compactedrecords - 97.6% of the entire file attributable to
compactedrecords - 125 individual JSONL records larger than 10 MB
- Largest JSONL record approximately 12.93 MiB
One inspected replacement_history contained:
- 118 history items
- 19
input_imageentries - 13,316,621 characters of inline
data:image/png;base64,...data - Approximately 197,716 characters of input text
The child files are not small records that merely reference their parent. A representative child begins with its own session_meta, followed by a copy of the parent’s original session_meta and inherited parent events. Sampled parent payloads were identical, while outer timestamps were rewritten to the child creation time and some response items received new IDs.
The files have separate inodes and link counts of 1, and their allocated sizes are close to their logical sizes. This appears to be independently serialized history rather than sparse files or parent references.
This can consume enough storage to exhaust a local disk during a single long-running Ultra session.
What steps can reproduce the bug?
The severity depends on having a sufficiently large parent history, multiple compactions, inline images, and multiple full-history forks.
- Start a persisted local Codex thread using Codex Desktop or another local surface backed by the shared Codex runtime.
- Use the same thread for an extended period.
- Add several screenshots or other image inputs to the conversation.
- Continue the conversation long enough to trigger multiple context compactions.
- Select Ultra reasoning with multi-agent V2 enabled.
- Give Codex work that causes it to proactively spawn multiple subagents.
- Allow the generated
spawn_agentcalls to omitfork_turns. In multi-agent V2, the omitted value defaults to"all". - Inspect the newly created child rollouts under
$CODEX_HOME/sessions. - Compare their sizes and initial JSONL records with the parent rollout.
- Repeat with additional subagents and observe that each full-history child can approach the persisted historical size of the parent before doing significant child-specific work.
Example read-only inspection commands:
du -sh "$CODEX_HOME/sessions"
find "$CODEX_HOME/sessions" -type f -name '*.jsonl' -print0 \
| xargs -0 stat -f '%z %N' \
| sort -nr \
| head -50
For a suspected child file:
head -n 1 "$CHILD_ROLLOUT" \
| jq '.payload | {
id,
session_id,
thread_source,
forked_from_id,
parent_thread_id,
multi_agent_version,
source
}'
The child metadata should identify it as a subagent and point to the parent through parent_thread_id and/or forked_from_id.
Inspecting the first several records should show the child’s new session_meta followed by inherited parent rollout records.
The problem becomes increasingly visible as these factors increase:
- Number of retained compaction records
- Number and size of inline images
- Length of the parent thread
- Number of spawned subagents
- Use of full-history/default
fork_turns="all"behavior
What is the expected behavior?
Spawning subagents should not cause local persisted session storage to grow in proportion to the parent’s complete append-only rollout history for every child, particularly when the history contains multiple historical compaction snapshots representing earlier versions of substantially the same model context.
A newly spawned child should not routinely approach 1–2 GB before performing meaningful child-specific work.
Disk growth should remain bounded enough that proactive multi-agent behavior cannot unexpectedly create more than 100 GiB of local session data from one conversation tree.
This expectation applies regardless of whether the triggering surface is Codex Desktop, the CLI, or codex exec, since the relevant multi-agent and rollout-persistence behavior appears to be shared.
Additional information
Environment and metadata from the affected session:
- Platform: macOS
- Surface: Codex Desktop
- Reasoning mode: Ultra
- Root metadata:
originator: "Codex Desktop"source: "vscode"history_mode: "legacy"- Child metadata:
thread_source: "subagent"multi_agent_version: "v2"- Codex versions present:
0.144.20.144.50.145.0-alpha.18
Relevant source locations:
codex-rs/core/src/session/multi_agents.rseffective_multi_agent_modemaps Ultra reasoning to proactive multi-agent behavior.- The behavior applies to
SessionSource::Cli,SessionSource::VSCode,SessionSource::Exec, and other sources.
codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rsSpawnAgentArgs::fork_modedefaults omitted or emptyfork_turnsto"all"."all"becomesSpawnAgentForkMode::FullHistory.
codex-rs/core/src/agent/control/spawn.rsAgentControl::spawn_forked_threadflushes the parent and reads the stored thread withinclude_history: true.- It uses
parent_history.itemsas the forked rollout items. - The fork filter retains
RolloutItem::Compacted. - The inspected
Compactedrecords contain almost all of the affected bytes.
codex-rs/core/src/session/mod.rs- The
InitialHistory::Forkedbranch inrecord_initial_historypersists the inherited rollout items. - The code comment says:
If persisting, persist all rollout items as-is (the store filters).
codex-rs/core/src/compact.rs- Compaction creates a
CompactedItemwithreplacement_history: Some(new_history.clone()).
codex-rs/core/src/session/mod.rsreplace_compacted_historyalso persists aCompactedItemcontaining a cloned replacement history.
codex-rs/rollout/src/recorder.rsJsonlWriter::write_rollout_itemassigns a new timestamp, serializes the item withserde_json::to_string, and writes it to the child JSONL.
codex-rs/app-server/README.mdthread/forkis documented as creating a new thread by copying stored history.
Questions that may help confirm whether the observed persistence is intentional:
- Is a subagent fork expected to persist every historical
Compactedrecord from the source rollout? - Is each earlier
replacement_historyexpected to remain independently serialized after later compactions supersede it? - Is inline base64 image data expected to be repeated across every compaction snapshot and every full-history child fork?
- Is there an intended upper bound for inherited persisted bytes when spawning a subagent?
- Should proactive Ultra subagents have the same persistence behavior as an explicit user-created full thread fork?
I can provide additional sanitized structural evidence if needed, including record-size distributions, parent/child metadata relationships, payload hash comparisons, compaction counts, and encoded image lengths.
I cannot attach the complete affected rollouts because they are individually up to approximately 1.9 GB and contain private conversation and image data.
9 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
i checked current
mainafter the recent paginated history work. It helps paginated parents, but it does not remove the legacy amplification reported here.For a legacy parent,
load_agent_model_contextstill callsread_stored_threadwithinclude_history: trueand returns the stored history items:https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/core/src/agent/control/spawn.rs#L99-L123
The fork path explicitly selects paginated history only when the parent is already paginated:
https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/core/src/agent/control/spawn.rs#L596-L713
Forked history also keeps the source format, so a legacy parent stays legacy:
https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/protocol/src/protocol.rs#L2679-L2688
The child then persists the inherited model context instead of sharing an immutable prefix:
https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/thread-store/src/live_thread.rs#L110-L143
SessionMeta.history_baseexists, but the production recorder currently writeshistory_base: None. i could not find a production writer that sets it toSome(...):https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/rollout/src/recorder.rs#L820-L843
A separate read-only structural scan of one affected store found about 12.3k compacted records. About 90.5% contained inline images. There were about 156.7k image occurrences but only 452 unique payload hashes. i did not inspect or upload image content, prompts, paths, project names, or thread IDs.
This is consistent with the same media being copied through compaction snapshots and inherited child context. The safe target seems to be an immutable shared ancestor prefix plus child-only events. Legacy parents also need a bounded or sanitized projection before a child is persisted. Parent rollouts should never be rewritten during fork repair.
Urgent reproduction evidence and tested patch
I traced the storage amplification on current
main(61a44880a85d2fd0d8770908dea5733495e571c8) and prepared a bounded emergency mitigation as a reviewable two-stage stack:8887873035de9c359c4b0464380eb24c559e74e4, 440 changed lines)37cb98085fc090ea0b39b0dc91c46fdc13d53413, 459 changed lines)Sanitized local evidence
A read-only audit found:
The later child added only 115,439 bytes / 82 records of logical delta, but allocated another 1.433 GiB physical file: about 13,333.9x write amplification relative to the incremental payload.
Root cause
Ordinary legacy resume already reopens and appends to its existing rollout. A distinct subagent rollout is also intentional. The defective path is automatic
spawn_agentwith a full-history fork from a legacy plain JSONL parent: it loads and reserializes the parent's entire accumulated rollout, including superseded compaction checkpoints and repeated inline media, into every child.Patch boundary
The patch reverse-scans local legacy JSONL and retains only the newest safe compaction checkpoint plus the completed-turn suffix needed for reconstruction. For old checkpoints without
window_number, it continues toward the head in count-only mode and normalizes the checkpoint ordinal copied to the child, preserving cold-resume window numbering without rewriting the parent.Conservative full-history fallbacks remain for unsupported or ambiguous histories, and legacy numeric
LastNTurnskeeps its existing full-read behavior. The scan also preserves legacy compatibility for appended metadata updates and retiredghost_snapshotrecords. This removes the multiplicative child-write/storage cost; it intentionally leaves O(parent) read I/O for old legacy ordinal counting.Duplicate-PR audit
I paginated and audited all 203 currently open PRs across changed files, patches, titles/bodies, linked issues, issue comments, reviews, and inline review comments. No open PR implements this local legacy checkpoint selection or automatic-subagent persistence fix. The closest PRs (#30977, #31175, #30866, and #31052) are complementary or target different persistence paths.
Validation
cargo fmt --all -- --checkcargo clippy --fix --tests --allow-dirty -p codex-rollout -p codex-thread-store -p codex-corecargo test -p codex-rollout -p codex-thread-store— 112 + 157 passedcargo test -p codex-core spawn_agent_— 37 unit + 15 integration passedcargo test -p codex-core --test all suite::multi_agent_resume— 2 passedThe
test_codexintegration performs two real legacy compactions, invokes publiccollaboration.spawn_agentwithfork_turns: "all", verifies the child inference request contains only the newest checkpoint plus suffix, checks the persisted normalized ordinal, and cold-resumes the child.Review status
Codex review completed on both final heads and reported no major issues:
8887873035: https://github.com/Yasei-no-otoko/codex/pull/2#issuecomment-508546853037cb98085f: https://github.com/Yasei-no-otoko/codex/pull/1#issuecomment-5085465135Earlier review findings were addressed with the public
test_codexintegration, the under-500-line PR split, portablebuild_with_auto_env, appended metadata handling, and legacy ghost-snapshot normalization. Both PRs are non-draft,CLEAN, andMERGEABLEat the reviewed commits.Per the contribution policy, I have not opened an unsolicited upstream PR. Given the disk impact, please triage this urgently and, if the approach is acceptable, explicitly invite an upstream submission so these same reviewed commits can be proposed against
openai/codex.Adding a current Windows Desktop datapoint from a smaller real-world thread tree.
Environment
26.721.4979.0Observed storage
| Rollout | Size |
| --- | ---: |
| Root |
2,086.2 MiB|| Child 1 |
989.5 MiB|| Child 2 |
986.6 MiB|| Child 3 |
525.3 MiB|| Total |
4,587.7 MiB(4.48 GiB) |The separately stored generated-image artifacts contained
286PNG files totaling476.6 MiB. All286files had unique SHA-256 hashes, so the amplification was not caused by duplicate PNG files ingenerated_images.Structural inspection showed inherited parent history in the child rollouts, including image-bearing records and compacted history. Across the four rollouts, inline base64 image data accounted for approximately
2,027.5 MiB.This was an observed production workload rather than a minimal synthetic reproduction. The child structure was consistent with full-history inheritance, but I did not confirm whether
fork_turns="all"was explicitly selected or obtained through the default behavior.Even with only three child threads, the session tree reached
4.48 GiB. This supports the report that inherited history, compacted snapshots, and inline images multiply persisted storage at the child-rollout layer.The image-persistence side of this observation is tracked in #22603.
Proposed direction
A child rollout should persist only child-specific events plus a bounded reference to an immutable parent checkpoint. It should not independently serialize every historical compaction snapshot or raw image payload inherited from the parent.
Privacy
No raw rollout, prompt, image, local path, username, project name, or thread ID is included. Only aggregate sizes, record structure, and content-hash counts were inspected.
Windows confirmation of this issue on a newer Codex Desktop/runtime build.
In-product Codex feedback/session reference:
019f4b7a-e382-7862-aeda-71eb42955f7cEnvironment
| Component | Value |
| --- | --- |
| Surface | Codex Desktop |
| Codex Desktop package |
26.727.6591.0|| Runtime version recorded in current child metadata |
0.146.0-alpha.9.2|| OS | Windows 11 Pro,
10.0.26200|| Filesystem | NTFS |
| Volume size | 3725.12 GiB |
| Free space at diagnosis | 67.85 GiB |
| Model / reasoning | GPT-5.6 Sol / Ultra |
| Multi-agent configuration | enabled,
max_threads = 9,max_depth = 1|Observed impact
%USERPROFILE%\.codex\sessionsreached 892.810 GiB across 1,112 rollout files. A metadata-only aggregation grouped 854 child-agent rollouts under one long-lived parent thread. Those child files consume 822.711 GiB, approximately 92.2% of the live session store.The newest child rollouts appeared at approximately 4.07-4.09 GiB immediately after creation, before meaningful child-specific work. The affected parent rollout was approximately 4.53 GiB during diagnosis.
The first records of the giant child files consistently contain the child's own
session_meta, asubagent.thread_spawn.parent_thread_idreferring to the same parent, and then inherited parent metadata and history records. An NTFS allocation check confirmed that a representative child file was not sparse and that its complete logical range was physically allocated.The growth is stepwise at child creation rather than a constant idle leak. During a 20-second idle sample the session store changed by only approximately 0.03 MiB, while new full-history child forks produced multi-gigabyte jumps. With nine concurrent threads, one round of child creation can therefore materialize tens of gigabytes in seconds.
File-size distribution
| Size range | Files | Combined size |
| --- | ---: | ---: |
| At least 4 GiB | 5 | 20.85 GiB |
| 3-4 GiB | 98 | 342.71 GiB |
| 1-3 GiB | 258 | 438.35 GiB |
| 100 MiB-1 GiB | 193 | 80.44 GiB |
| Below 100 MiB | 558 | 10.46 GiB |
This confirms the full-history fork amplification described in this issue on Windows and runtime
0.146.0-alpha.9.2. No additional reproduction was triggered because the system volume was already close to exhaustion.Expected behavior is that child agents reference or receive a bounded representation of inherited context instead of independently serializing the parent's complete append-only rollout and historical compaction snapshots. Codex should also enforce per-thread and global session-storage limits and warn before rollout storage or free disk space reaches a critical level.
I noticed some similar space-usage-explosion, noticed the size distribution was suspicious, and had my own codex poke around. Here's what it spit out:
I have an exceptionally large and long-running legacy Codex session lineage with unusual disk usage. I asked Codex to perform a bounded, de-identified structural audit and help me understand the amplification. It produced the following:
A separate bounded audit of another active legacy lineage found the same structural shape reported here:
112large child rollouts with independently copied ancestry. Only de-identified aggregate structure is reported here; no prompt, tool output, image payload, private path, project name, or thread ID is included.Per-file compression can reduce physical bytes, but it cannot remove those
112serialized ancestries, reduce replay work, or provide parent/child sharing. This is another confirmation that codec compression and structural fork amplification need separate regression coverage.For performance/storage sheriffing, a small deterministic fixture should model a legacy parent with multiple compactions and repeated synthetic image payloads, then create at least
100children. It should assert:children × complete parent history.The fixture does not need private production data or large payloads; compact synthetic messages, image payloads, and compaction records are sufficient to reproduce the multiplicative shape.
Adding another de-identified Windows Codex Desktop data point from a bounded, metadata-only snapshot.
Storage impact
.codex/sessions:26,236,757,713logical bytes (about26.24 GB/24.43 GiB) across1,643JSONL files at the snapshot time.54rollout files were at least250 MiB:51had a terminaltask_complete,2were active, and1ended aborted/unknown.compacted/replacement_historymaterial, rather than only fork-local deltas.forked_from_idandsubagent.thread_spawnprovenance. A separate source audit found that OpenCodex does not create or copy.codex/sessionsrollout JSONLs, so this amplification is in the Codex fork/rollout persistence path rather than the model-routing proxy.Compaction trigger vs. persistence
One recent compaction looked premature if only the visible
282,633-token counter was considered. Under the runtime's defaultserver_reasoning_included=falseestimator path, the active context reconstructed as:282,633visible total +47,158estimated prior encrypted-reasoning tokens +6,852estimated local tool-output tail =336,643tokens.That exceeds the configured automatic-compaction threshold of
334,800by1,843tokens. This is consistent with a normal compaction trigger; the defect reported here is the subsequent persistence and fork amplification of inherited/compacted history.Requested safeguards
replacement_historyout of line so historical compaction snapshots are not repeatedly embedded.This also overlaps the broader storage-safety concerns in #34337 and the parent-reference persistence problem in #35647. No raw rollout, prompt, tool output, username, local path, project name, or thread ID is attached.
The 110 GiB multi-agent store and 2.64 GiB child rollouts with 232 compacted snapshots are a strong real case for bounded inspection, especially since the same inline-image history is physically inherited by each fork.
Codex Rescue cannot deduplicate those files, fix compaction, or recover deleted sessions. I’m testing whether it safely diagnoses one preserved affected/control rollout without reading image contents or modifying the source. It never replays calls and verification remains fail-closed.
If the 48.4 MiB control or any untouched affected sample remains, could you run:
pipx install codex-rescue==0.1.0a3codex-rescue sessionscodex-rescue doctor --latestSanitized output only—no raw JSONL, screenshots/base64, databases, prompts, project data, paths, or secrets.
https://github.com/shleder/codex-rescue
Additional reproduction confirming this still occurs on a newer Codex Desktop runtime.
Environment:
Privacy-safe measurements from one long-running parent tree:
The growth accelerated over five calendar days and pushed the APFS Data volume to about 90% used. The parent thread and active descendants were paused, future subagent creation was disabled temporarily, and only validated closed descendants were removed. That reduced the Codex data directory from about 244 GiB to 74 GiB and increased free Data-volume space to 317 GiB.
This strongly matches the full-history fork / retained compaction amplification described here. Raw JSONL files were not attached because they contain private conversation and tool content. This report contains no task names, session IDs, usernames, or personal filesystem paths.