Paginated history drops valid flattened rollout records and reuses ordinals
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
Research direction
Start with scan_next::RolloutLine in codex-rs/rollout/src/ordinal.rs and the line deserialization in codex-rs/thread-store/src/local/thread_history_materialization.rs. Reproduce the provided token-count serialization case, then trace resume ordinal discovery and SQLite projection catch-up. Done means both readers match the canonical value-first decoding semantics and regression coverage confirms valid final records and affected projections are handled correctly.
Written by the indexing model from the issue text.
Description
What version of Codex CLI is running?
Observed on 0.146.0-alpha.10.1. The affected source paths remain unchanged in rust-v0.146.0-alpha.14.
What platform is your computer?
Linux x86_64.
What issue are you seeing?
Paginated rollout history has inconsistent RolloutLine decoding. Some readers deserialize JSON directly into RolloutLine, while the canonical loader first parses into serde_json::Value and then calls serde_json::from_value::<RolloutLine>.
A serialized RolloutLine::EventMsg(EventMsg::TokenCount(...)) containing populated rate-limit and credit data is valid JSON and succeeds through the value-first path, but direct serde_json::from_str::<RolloutLine> / serde_json::from_slice::<RolloutLine> rejects it.
Two paginated-history readers currently use the direct path:
codex-rs/rollout/src/ordinal.rs:scan_next::<RolloutLine>()codex-rs/thread-store/src/local/thread_history_materialization.rs:serde_json::from_slice(line_bytes)
This produces two related failures:
- Resume ordinal discovery silently skips the valid final record, steps back to an earlier ordinal, and can append a new record with an already-used ordinal.
- SQLite history projection silently skips the valid record while advancing its byte offset beyond it, leaving the materialized projection behind the canonical JSONL permanently.
Once affected, catch-up can begin with one replayed boundary ordinal equal to projection_state.next_ordinal - 1; rejecting that boundary prevents the remaining valid suffix from materializing.
What steps can reproduce the bug?
Add a focused test that serializes a paginated RolloutLine containing a token-count event with rate-limit and credit fields:
let encoded = serde_json::to_string(&RolloutLine {
timestamp: "2026-07-09T00:00:05Z".to_string(),
ordinal: Some(5),
item: RolloutItem::EventMsg(EventMsg::TokenCount(TokenCountEvent {
info: Some(TokenUsageInfo::full_context_window(1_000)),
rate_limits: Some(RateLimitSnapshot {
limit_id: Some("limit-1".to_string()),
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 1.0,
window_minutes: Some(60),
resets_at: Some(1),
}),
secondary: None,
credits: Some(CreditsSnapshot {
has_credits: true,
unlimited: false,
balance: Some("1".to_string()),
}),
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
}),
})),
})?;
assert!(serde_json::from_str::<RolloutLine>(&encoded).is_err());
assert!(
serde_json::from_str(&encoded)
.and_then(serde_json::from_value::<RolloutLine>)
.is_ok()
);
Then:
- Append that record as the final line of a paginated rollout and resume it. Ordinal discovery ignores ordinal
5. - Materialize the same rollout into the thread-history SQLite projection. The line is rejected, but the stored byte offset advances past it.
What is the expected behavior?
Every rollout reader should use the same value-first decoding semantics as the canonical loader. Resume should continue after the real final ordinal, and SQLite materialization should project every valid canonical record.
For projections already affected by the old decoder, narrowly accepting and skipping one replayed first boundary ordinal (next_ordinal - 1) allows the remaining suffix to catch up without rewriting canonical JSONL.
Additional information
rust-v0.146.0-alpha.14 changes nearby thread-list behavior for missing rollout paths, but ordinal.rs and thread_history_materialization.rs still use direct RolloutLine deserialization.
- 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 ·