migrate-rollouts silently drops >16 MiB JSONL records while reporting migration successful
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
Research direction
Start in codex-rs/thread-store/src/local/rollout_migration.rs and read the migration tests in codex-rs/thread-store/src/local/rollout_migration_tests.rs. Reproduce oversized custom_tool_call_output and compacted records, including a later valid event, then compare dry-run and apply behavior. Done means oversized valid records are not silently lost and tests cover reporting, source replacement safety, and readback fidelity.
Written by the indexing model from the issue text.
Description
What version of Codex CLI is running?
0.148.0-alpha.5
What subscription do you have?
Not applicable (local rollout migration).
Which model were you using?
Not applicable (local rollout migration).
What platform is your computer?
macOS / Darwin 27.0.0 / arm64
What terminal are you using?
zsh
Codex doctor report
Not included: this is a deterministic local storage transformation and source-level reproduction with no auth, network, or model dependency.
What is the issue?
codex migrate-rollouts --apply can silently omit valid legacy JSONL records larger than 16 MiB while reporting the thread as successfully migrated.
The current implementation sets:
const MAX_ROLLOUT_LINE_BYTES: usize = 16 * 1024 * 1024;
When a record is larger than that limit, read_rollout_record() consumes and discards the whole record through its newline and returns no line to the canonicalizer. The record is therefore absent from the staged paginated rollout and is not recoverable from the SQLite projection. Migration can still finish with status = migrated and no warning or message.
The size check happens before JSON parsing and is not restricted to tool output records, so it also affects valid compacted checkpoints.
Dry-run does not detect this. For a legacy rollout it reads session metadata and returns eligible without scanning all records, so the first full-record scan and oversized-record handling occur only during --apply.
The existing migration test also demonstrates the behavior: a valid oversized function_call_output is absent after migration while the outcome remains migrated.
Steps to reproduce
- Create a valid legacy rollout JSONL containing normal session metadata and normal events.
- Add a syntactically valid JSONL record larger than 16 MiB, for example:
- a
response_item/custom_tool_call_outputcontaining inline image data; or - a
compactedrecord whosereplacement_historyexceeds 16 MiB.
- a
- Add another valid event after the oversized record to show that processing continues.
- Run a dry-run:
The rollout is reported as eligible; the oversized record is not reported.codex migrate-rollouts --thread <THREAD_UUID> --json - Back up the test rollout, then run:
codex migrate-rollouts --apply --thread <THREAD_UUID> --json - Compare the migrated rollout with the input.
What did you expect to happen?
Migration should never silently turn a valid rollout into a partial rollout.
Preferably, migration should preserve valid records losslessly. If a finite per-record limit is required, then:
- dry-run should scan for and report oversized records;
- apply should fail closed before replacing the source, or require an explicit opt-in to omit data;
- any partial result should not use the unqualified
migratedstatus; - output should report skipped record counts, sizes, and positions;
- a recoverable backup should exist before source replacement.
What happened instead?
- The oversized record is consumed through its newline but not passed to canonicalization.
- Migration continues with later records.
- The staged file replaces the original rollout without the oversized record.
- The outcome can be
migratedwithmessage = null. - There is no skipped-record count, byte count, ordinal, sentinel, or warning.
- Surviving records are assigned canonical ordinals, so there is no ordinal gap to reveal the loss.
- Dry-run reports
eligiblebecause it does not perform the full scan.
For a dropped custom_tool_call_output, resume may only synthesize an aborted output if the corresponding call remains; the actual output is not restored.
For a dropped compacted { replacement_history } record, resume may replay surviving raw events, but that is not necessarily equivalent to the compacted model-visible history and its checkpoint/baseline state. A newer surviving checkpoint can reduce runtime impact, but it does not restore archival fidelity.
Additional information
I performed a read-only, content-safe metadata audit of three existing legacy rollouts. I did not run --apply on them.
Across those three files, there were:
- 10 syntactically valid records larger than 16 MiB;
- 242,416,165 total bytes in those records (about 231 MiB);
- 7
response_item / custom_tool_call_outputrecords dominated by inline image data; - 3
compacted / replacement_historyrecords dominated by image-bearing history.
This shows the limit is reachable in real Codex sessions, not only with artificial terminal output. No session payloads, inline images, thread IDs, workspace paths, or credentials are included in this report.
Relevant implementation and tests:
- CLI migration/reporting: https://github.com/openai/codex/pull/37348
- Oversized-record handling: https://github.com/openai/codex/pull/37191
- Current implementation: https://github.com/openai/codex/blob/main/codex-rs/thread-store/src/local/rollout_migration.rs
- Current tests: https://github.com/openai/codex/blob/main/codex-rs/thread-store/src/local/rollout_migration_tests.rs
Related but distinct reports:
- https://github.com/openai/codex/issues/37670 — migration fidelity issue involving duplicated compatibility input, not silent oversized-record loss
- https://github.com/openai/codex/issues/28531 — image-heavy inline rollout data causing Desktop crashes/freezes
- https://github.com/openai/codex/issues/33735 —
compacted.replacement_historyduplicating inline images and growing rollout size
Suggested regression coverage:
-
16 MiB
custom_tool_call_output; -
16 MiB
compacted / replacement_history; - a valid record after the oversized record;
- dry-run/apply consistency;
- resume/readback fidelity;
- explicit non-success status whenever any record is omitted.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- Avg merge
- 1m
- Merged PRs (30d)
- 1k
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openai/codex
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
bug CLI windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
macOS sandbox blocks hw.optional.arm64 sysctl, causing Flutter to misdetect Apple Silicon as x64 Openbug CLI sandbox
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug CLI TUI
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
CLI config enhancement skills
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
A-linter
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
oxc-project/oxc#26863 ·