openai / openai/codex

Resume opens a long thread at its first turn: 0.146.1 desynced thread_history projection cursors, and later versions never repair them

Open
#38,792 17 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI session
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_offset points at a clean record boundary whose record carries ordinal N+1,
  • while next_rollout_ordinal still 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 against next_rollout_byte_offset == rollout size after 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.