app-server: thread history projection stalls forever after a duplicated rollout ordinal ("expected N, got N-1"); thread/items/list and thread/turns/list stop reflecting new turns
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Environment
- codex-cli 0.153.4 (standalone, x86_64-apple-darwin), app-server started with
codex app-server --remote-control --listen unix://… - macOS (Darwin 25.5.0)
- Stores:
~/.codex/state_5.sqlite,~/.codex/thread_history_1.sqlite, rollouts under~/.codex/sessions/
Summary
For some threads, the rollout file (sessions/**/rollout-*.jsonl) contains two consecutive lines with the same ordinal. The thread-history projection (codex_thread_store::local::live_writer) then fails on every append with
failed to project durable rollout for <thread>: thread-store internal error:
thread history projection for <thread> expected ordinal 5126, got 5125
and never advances again. turn/start still works and the rollout keeps growing (turns complete normally), but thread/items/list and thread/turns/list keep returning the state as of the duplicate. Any client that reads threads through the app-server (our CLI wrapper, our desktop app) sees the thread frozen days back, while the agent is actually working and finishing turns.
Observed (3 threads on one machine)
| thread | rollout lines | projection next_rollout_ordinal | first failure | failures logged |
|---|---|---|---|---|
| 01a09ec8-97ab-7a63-a27b-65c1a5d54a39 | 8194 (304 MB) | 5126 | 2026-09-16 18:03 JST | 367 |
| 01a0930e-6c16-7960-a3f1-8ab0f52a65d1 | 25554 (1.02 GB) | 21892 | 2026-09-16 10:29 JST | 442 |
| 01a094d7-f722-70c1-acd6-0aee5263d24b | 15577 | 6560 | 2026-09-16 10:29 JST | 414 |
Duplicated ordinals found by scanning the rollouts (line number, ordinal, record):
- 01a09ec8: line 5127, ordinal 5125 =
event_msg/task_started(2026-09-16T02:14:37Z) — the previous line also has ordinal 5125 - 01a0930e: line 21893, ordinal 21891 =
event_msg/thread_settings_applied(2026-09-16T00:49:17Z); and again line 22156, ordinal 22153 =event_msg/thread_settings_applied(2026-09-16T01:36:01Z)
No gaps, only duplicates; from the duplicate onward every ordinal is one behind the line index. In all cases the duplicate is the first record written when the thread was resumed (task_started / thread_settings_applied), which suggests the resumed session's ordinal counter starts at the last written ordinal instead of last+1.
thread_history_projection_state.next_rollout_byte_offset points exactly at the duplicated line, and next_rollout_ordinal is one more than that line's ordinal, so the projection is consistent with the file up to the duplicate and cannot pass it.
Expected
- The writer should never reuse an ordinal on resume.
- The projection should tolerate (skip or renumber) a duplicated ordinal instead of failing every append forever; at minimum it should surface the stall to API clients (e.g.
thread/readreporting a projection error) rather than silently returning stale lists.
Impact
Remote-control clients cannot see new user messages or agent replies on the affected threads; delivery verification by reading thread/items/list fails although the turn ran to completion.
Repro idea
Resume a thread (notLoaded → thread/resume) and start a turn; check whether the first appended rollout record repeats the previous ordinal. We have not isolated the exact trigger; the three affected threads were all resumed on 2026-09-16 09:49–11:14 JST, two of them around an app-server restart at 11:13.
Logs
From logs_2.sqlite, target codex_thread_store::local::live_writer, e.g. 2026-09-18 07:14:49 JST:
persist_rollout_items{item_count=1}:append_items{item_count=1}: failed to project durable rollout for 01a0930e-…: thread-store internal error: thread history projection for 01a0930e-… expected ordinal 21892, got 21891
Also on shutdown: failed to project durable rollout during shutdown for 01a09ec8-…: … expected ordinal 5126, got 5125
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 with codex_thread_store::local::live_writer and the thread_history_projection_state handling, then reproduce by resuming a thread and starting a turn. Inspect how the rollout byte offset and next ordinal behave at the duplicated record. Done means the projection advances without repeated failures and thread/items/list and thread/turns/list reflect subsequent turns.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, sqlite
- Domain
- api, backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100