[Desktop] Completed thread shows stale pre-crash state after restart while rollout & state DB stay intact — thread_history projection is never rebuilt

Resolved 💬 2 comments Opened Aug 22, 2026 by QianQIUlp Closed Aug 22, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Upstream bug report — openai/codex

Title

[Desktop] Completed thread shows stale pre-crash state after restart while rollout & state DB stay intact — thread_history projection is never rebuilt

Body

Environment

  • OS: Windows 11 Pro x64; every shutdown/restart in the timeline was clean (no power loss, no bugcheck)
  • App: Codex Desktop for Windows, version 26.818.41509
  • Observed: 2026-08-20 (all timestamps UTC)

Summary

During an active Desktop task, the Codex app-server process died abruptly
(the whole process tree was terminated externally by a local automation tool).
After relaunching the app, a completed thread appeared to have "rolled
back": its visible history and the Continue position jumped back to a much
earlier point in the conversation.

Inspecting the three storage layers under %USERPROFILE%\.codex showed the
data was not lost:

| Layer | Role | State after incident |
|---|---|---|
| sessions/<date>/rollout-*.jsonl | raw turn transcript | ✅ complete, ends with the final task_complete |
| state_5.sqlite | authoritative thread state | ✅ current; terminal state recorded |
| thread_history_1.sqlite | Desktop's UI projection of threads | ❌ frozen at a pre-crash position |

Quantified: rows materialized in the projection covered roughly 16% of the
event range the authoritative layer held for this thread, and that number
never changed across multiple app restarts over the following two days.

What appears to be wrong

  1. The thread_history_* projection appears to be written incrementally as

events arrive, with no reconciliation pass on startup: nothing compares
the projection against the authoritative state store and catches it up.

  1. There is no rebuild/fallback path when the projection is detected as stale

or torn (e.g., writer died mid-write).

  1. History view and Continue/resume appear to serve from the projection,

so a stale projection hides real work the model actually did — worse than
losing data, it misrepresents it.

  1. Projection writes are not crash-atomic with the authoritative store, and

the crash window (app-server death between the two writes) leaves them
permanently divergent.

How it manifested

  • Thread list showed the thread with its old preview/position.
  • Opening it displayed history only up to the freeze point.
  • Continue resumed from the stale position even though later turns existed in

both the rollout and the state DB.

  • Reinstalling the app did not repair the projection (fresh binaries, same

frozen thread_history_1.sqlite), confirming it lives outside the package.

Minimal reproduction sketch

  1. Start a Desktop task; let several turns complete.
  2. Hard-kill the app-server / process tree mid-task (simulating a crash).
  3. Relaunch Desktop.
  4. Observe: thread history and Continue position revert to some earlier point;

rollout JSONL and state_5.sqlite still contain everything after it.

(The triggering crash in our case came from a third-party local tool killing
the process tree; the defect itself — no projection reconciliation on restart —
is independent of what caused the death.)

Expected behavior

On startup (or when opening a thread), Desktop reconciles the UI projection
against the authoritative state store — or simply treats the projection as a
cache that can always be rebuilt from state_5.sqlite / rollouts — so a
completed thread can never present stale history after a crash.

Actual behavior

The frozen projection persists indefinitely across restarts and even across an
application reinstall (fresh binaries, same frozen thread_history_1.sqlite,
confirming the projection lives outside the package). The thread's visible
history and Continue position stay pinned to the pre-crash point while both
durable stores contain the full completed session. Recovering the correct view
currently requires out-of-band surgery on private SQLite files, which end
users should never have to perform — and which we deliberately did not attempt.

Impact

  • Completed work becomes invisible after any crash landing inside the

projection-writer window; the UI actively misrepresents the durable record.

  • Continue can resume from a stale point, inviting duplicated work.
  • User trust in the history view erodes because it can contradict

state_5.sqlite / rollouts, which are supposed to be the source of truth.

Notes for maintainers

  • We can supply, privately, the exact row-range comparison between

thread_history_1.sqlite and state_5.sqlite (materialized vs authoritative
event coverage), the crash-window log correlation, and the full restart
timeline. This public report intentionally omits thread identifiers,
absolute paths, and any conversation content.

  • A preserved evidence snapshot of the divergent databases is available if a

fix build needs a real-world regression target.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 5 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #38792
  • #39885

Powered by Codex Action

QianQIUlp · 5 days ago

Marking as duplicate of #38792, which describes the same \ hread_history_1.sqlite\ projection cursor desync and permanent non-repair at greater depth (our case is the Windows Desktop manifestation). Consolidating our quantitative evidence (16% coverage vs authoritative store, reinstall-persistence, Desktop Continue/list symptoms) into that thread.