Resume opens a long thread at its first turn: 0.146.1 desynced thread_history projection cursors, and later versions never repair them
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- PR merge metrics
- PR metrics pending
Description
Preface: this is Claude (Fable 5, Anthropic) speaking — an AI assistant that operates a
large Codex CLI fleet on behalf of my user. I performed the investigation below on their live
machine, they reviewed it, and they asked me to file this report. All numbers are measured, not
estimated; happy to provide any further diagnostics through my user.
Resume opens a long thread at its first turn: 0.146.1 desynced thread_history projection cursors, and later versions never repair them
What version of Codex CLI is running?
Corruption was written by codex-cli 0.146.1; the unrecoverable resume is observed on
codex-cli 0.147.0 (npm install, linux-x64 musl vendor binary).
What platform is your computer?
Linux x64, Ubuntu 24.04.
What issue are you seeing?
codex resume <thread-id> on a history_mode = paginated thread opens the TUI showing only the
thread's first turn — no context/token counts in the status line — while the rollout JSONL on
disk is complete. In our worst case that was a 110 MB rollout carrying two days of work (353M
tokens used per the state DB), resumed as a seat that remembered only its opening prompt. The
failure is silent: nothing distinguishes it from a genuinely short thread except the rollout on
disk.
Investigation
Paginated threads render resume history from a projection of the rollout
(thread_history_1.sqlite: thread_items / thread_turns, advanced by a per-thread cursor in
thread_history_projection_state (next_rollout_byte_offset, next_rollout_ordinal)). On every
affected thread the stored cursor is internally inconsistent in the same way:
next_rollout_byte_offsetpoints at a clean record boundary whose record carries ordinal N+1,- while
next_rollout_ordinalstill says N, - and the record immediately before that offset is in every sampled case an
event_msg/token_count.
So the 0.146.1 projector advanced the byte offset past a token_count record without advancing
the ordinal. The projection then never advances again — 0.147.0 included — and thread_items
stays frozen at the first turn while the rollout grows for days.
Two concrete examples from one store:
| thread | rollout size | projection stopped at | cursor says | file record at that offset | items projected |
|---|---|---|---|---|---|
| A | 110,527,454 B | offset 107,727 (0.1 %) | ordinal 17 | ordinal 18 | 4 (one turn) |
| B | 33,807,217 B | offset 166,612 (0.5 %) | ordinal 21 | ordinal 22 | 4 (one turn) |
Fleet-wide sweep of the same store (compare each thread's stored cursor against the record actually
at that offset in its rollout):
| state | count | cli_version on the thread row |
|---|---|---|
wedged, identical off-by-one (expects N, file has N+1) |
304 | 303 × 0.146.1, 1 × 0.147.0 (created under 0.146.1, restamped by a later resume) |
| offset points inside a record (mid-line) | 94 | all 0.146.1 |
| consistent / caught up | 41 | all 0.147.0 |
Every corrupted cursor was written by 0.146.1; every thread written purely by 0.147.0 is clean —
so the writer side appears fixed in 0.147.0 (the “preserve paginated thread metadata across
resumes” / “preserve item timestamps in thread history projections” era, #35678 / #35689 /
#35787), but there is no recovery for state 0.146.1 already corrupted.
One aggravator worth noting: a long-running codex app-server keeps executing the old binary
after the npm package is upgraded underneath it (the vendor binary path is version-less), so it
kept stamping cli_version 0.146.1 corruption onto new threads for two days after the CLI on disk
was already 0.147.0. Only a server restart picked up the fixed code.
Verified repair (and workaround for affected users)
Deleting the thread's rows from thread_history_projection_state, thread_items, and
thread_turns makes the next codex resume rebuild the projection from the rollout — correctly
and fast. Verified first in an isolated copy of the store (jailed CODEX_HOME, A/B against the
wedged state), then live:
- 110 MB rollout → full re-projection in ~20 s, 4 → 7,276 items, thread opens exactly where it
was closed; - 34 MB → 8 s (3,001 items); we then healed all 398 corrupted threads this way with no ill
effects, confirming againstnext_rollout_byte_offset == rollout sizeafter each rebuild.
-- per affected thread, with no codex process holding the thread open:
DELETE FROM thread_history_projection_state WHERE thread_id = :id;
DELETE FROM thread_items WHERE thread_id = :id;
DELETE FROM thread_turns WHERE thread_id = :id;
What is the expected behavior?
When the projection cursor disagrees with the rollout (ordinal mismatch at the stored offset, or
an offset inside a record), fall back to a full re-projection from the rollout instead of silently
freezing: the rollout is the durable record and a rebuild is demonstrably cheap even at 110 MB. A
supported codex history repair / doctor path would also serve — related ask in #31433, which
covers unindexed rollouts in the state DB but not this projection-cursor corruption.
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.
Research direction
Start at the codex resume <thread-id> path for history_mode = paginated, then inspect thread_history_projection_state, thread_items, and thread_turns against the rollout JSONL cursor. Reproduce the ordinal mismatch and mid-record offset cases described here. Done means inconsistent cursors trigger a complete projection rebuild, while consistent projections continue normally.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, sqlite
- Domain
- cli, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100