Repository navigation
Codex Desktop: chat history stops rendering after crash mid-turn (projection cursor never follows rollout file rotation) #42387
Description
What version of the Codex App are you using (from "About Codex" dialog)?
0.150.0-alpha.8 (confirmed from a local session's session_meta record; please verify against the "About Codex" dialog and correct if the app has since updated)
What subscription do you have?
Team
What platform is your computer?
Windows 11, VS Code extension source ("source":"vscode", "originator":"Codex Desktop")
What issue are you seeing?
After a turn is interrupted (app crash / force-quit mid-turn), the Desktop app's rendered chat history for that thread permanently stops updating at the exact point of the crash — even though the underlying conversation continues normally and the model still has full context of everything said afterward. Re-opening the app (once, or repeatedly) never recovers the missing history; the thread looks like it silently lost every message after the crash, while resume-based context loading proves the data was never actually lost.
In my case the crash was triggered by the system running out of memory — my machine was running many other processes at once and doesn't have much RAM to spare, so Codex Desktop (or the underlying OS) got killed mid-turn under memory pressure rather than crashing due to anything Codex-specific. I'm noting this because it means the trigger isn't an exotic edge case — any resource-constrained machine that occasionally runs low on RAM will hit this "turn interrupted mid-write" condition, so the missing-history bug is probably more common than the lack of existing reports suggests.
Root cause, traced in the open-source repo (openai/codex, main branch):
codex-rs/thread-store/src/local/live_writer.rs— the rollout log path for a thread's live writer is resolved once, atresume_thread, and held statically (RolloutRecorder::new(&config, RolloutRecorderParams::resume(rollout_path))). There's no live re-pointing of an active writer to a different file mid-session.codex-rs/thread-store/src/local/thread_history_materialization.rs—read_projection_steps()opens the rollout file at whatever path it's handed once, and only ever reads forward from a stored byte offset within that specific file. It has no file-discovery or rotation-detection logic.codex-rs/thread-store/src/local/thread_history.rs—apply_projection()enforces strict offset continuity against the stored cursor:if expected_offset != start_offset { return Err(ThreadStoreError::Internal { message: format!("thread history projection for {thread_id} is behind durable rollout"), }); }
When a turn is interrupted mid-crash and the app resumes into a new physical rollout file for the same thread_id (I observed this as a rename pattern like rollout-<ts1>-<thread_id>.jsonl → rollout-<ts2>-<thread_id>_<new_uuid>.jsonl), the new file's byte offsets restart near 0. The thread_history_projection_state row for that thread still holds next_rollout_byte_offset from the old file's EOF. Every subsequent materialization attempt then fails the expected_offset != start_offset check and errors out — permanently, since nothing resets or migrates that row when a rotation happens. The SQLite-backed thread_items/thread_turns tables that the Desktop UI reads from are frozen at the crash boundary forever, while the raw rollout log (and thus the model's resumed context) keeps growing correctly.
I confirmed this directly against a local thread_history_1.sqlite (on a copy, not the live file): thread_history_projection_state.next_rollout_byte_offset for the affected thread exactly equaled the byte size of the pre-crash rollout file, and thread_items/thread_turns had zero rows past that point despite the actual conversation continuing for many more turns across roughly a day of work in the second file.
What steps can reproduce the bug?
- Start a Codex Desktop session and have a multi-turn conversation.
- Force the app to crash or be killed mid-turn (simulating a real crash — e.g. an out-of-memory kill on a resource-constrained machine with many other processes running, which is how I hit this), such that the active turn ends up recorded as
turn_aborted/interruptedin the rollout log. - Reopen the app and resume the same thread. Confirm you can keep chatting and the model still has full context (e.g. ask it to repeat your last message before the crash).
- Check the Desktop app's rendered history pane for that thread. Expected: full history including everything after the crash. Actual: history rendering stops exactly at the crash point and never updates again, even across further app restarts.
- (Diagnostic, optional) On a copy of
~/.codex/thread_history_1.sqlite, query:and compare against the byte size of the pre-crash rollout file underSELECT next_rollout_byte_offset, next_rollout_ordinal FROM thread_history_projection_state WHERE thread_id = '<affected thread id>';
~/.codex/sessions/.../rollout-<ts1>-<thread_id>.jsonl— they should match exactly, confirming the stuck cursor.
What is the expected behavior?
When a thread's rollout log rotates to a new physical file after an interrupted-turn resume, the history materializer should detect the rotation (or the resume flow should reset/re-seed thread_history_projection_state for that thread_id against the new file) so the SQLite projection — and therefore the rendered UI history — keeps advancing instead of permanently stalling at the crash point.
Additional information
- This does not cause data loss — the raw rollout
.jsonllog and the model's resumable context are intact. It's specifically the SQLite materialization layer used to render the Desktop history pane that gets stuck. - I did not find an existing issue for this via GitHub code/issue search (
thread_history_projection_state,is behind durable rollout, "history disappear after crash", etc. all returned zero results as of 2026-09-03). - No conversation content or thread IDs from my actual usage are included in this report — happy to provide a redacted thread_id or minimal repro session privately if that's useful for debugging.
Activity
github-actions commentedon Sep 2, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
84dnnvbdvp-debug commentedon Sep 3, 2026
One caution on narrowing this to rollout-file rotation specifically: the older #41333 thread has the same frozen paginated-history / ordinal-mismatch family with intact canonical rollout data, including a measured specimen where the SQLite projection held
next_ordinal=25202while the complete rollout record at its stored byte offset was ordinal25201. That report also notes some ultimately omitted turns completed before the time-correlated crash, so rotation/crash was not established as the necessary trigger.That makes file rotation a useful discriminator, but probably not the safest recovery invariant by itself. A regression/repair should also cover generic derived-projection cursor divergence from an intact canonical rollout: on a proven cursor mismatch, invalidate/reseed or rebuild only the per-thread derived projection from canonical rollout evidence rather than rewriting the rollout. If rotation is involved, binding the cursor to rollout file identity/epoch (or reseeding on path change) may be part of the fix, but same-file/off-by-one divergence should not remain permanently unrecoverable.
This does not establish that #42387 and #41333 have one internal cause; it only argues that the recovery path should not depend on rotation being the sole cause.
ORI-Fourreaumaxime commentedon Sep 3, 2026
I can confirm the same projection/rollout-handoff failure signature on Windows with a newer Codex Desktop/runtime.
Environment:
Read-only forensic analysis of one affected long-running thread:
RAW history since 2026-09-01:
Persisted thread_history_1.sqlite history since 2026-09-01:
The Desktop historical view stops at the older rollout boundary. Interestingly, the latest live turn is still visible separately, while the 19 intermediate turns are absent from the persisted/history projection.
Key cursor evidence:
The stored projection byte offset is therefore exactly equal to EOF of the previous physical rollout.
state_5.sqlite resolves the thread to the newer rollout, but the historical projection cursor still carries the previous rollout's terminal byte offset.
The newer rollout contains all subsequent turns and remains readable. No canonical conversation data appears to be lost.
This strongly matches the rollout-file handoff/rotation failure described here: rollout identity/path advances, but the persisted history projection cursor does not appear to be rebound/reseeded for the new physical file.
One additional observation that may help isolate the Desktop architecture: the newest live turn can still be rendered while the intermediate persisted history is absent. This suggests the realtime/live rendering path can advance independently of thread_history_1.sqlite.
I cannot confirm that the trigger in my case is identical to the crash/OOM trigger reported here; I am only confirming the same resulting storage and projection state.
No SQLite, rollout, index, project state or configuration was modified during this diagnosis.
dnhrdt commentedon Sep 7, 2026
Additional read-only corpus evidence from Windows (2026-09-07): this is a recurring class, not a single damaged thread.
I scanned 2,785 rollout JSONLs. After excluding two explicit forks (
forked_from_id), I found 24 rotated continuation segments across 19 logical thread IDs, dated 2026-08-20 through 2026-09-07. The continuation metadata spans CLI versions0.148.0-alpha.15through0.153.4.For all 24 rotations:
history_base.thread_idresolves to the exact physical parent rollout node (the final UUID in the parent's rollout filename).end_byte_offsetends at a JSONL newline, the last parent-prefix ordinal isend_ordinal_exclusive - 1, and the childsession_metaordinal isend_ordinal_exclusive.context_windowremain unchanged.turn_aborted.turn_aborted(median 15.112 seconds).Fourteen of the 24 rotations stayed on exactly the same CLI version, so an application update is not required. This points to the generic interrupted-turn resume/rollout-rotation path rather than an OOM crash or update as the necessary trigger.
The local state also separates into two independent pointers:
state_5.sqlitepoints to the base rollout for 17 of the 19 logical threads and to a continuation for 2.thread_history_projection_statematches the current size and next ordinal of the base rollout for all 19 threads, and never the continuation leaf.state_5.sqlitepoints to the continuation, the history projection still points to the base rollout.That means updating only the thread's rollout pointer would not repair the rendered history; the projection cursor can remain independently attached to the old physical file.
Two parent/base rollouts were written to again after their continuation had already been created (856 and 906 later records respectively). Therefore selecting a rollout by latest timestamp, largest size, or most recent modification would be unsafe. The implementation needs to honor the exact
history_baselineage and preserve real branch alternatives.This corpus does not support the duplicate-ordinal mechanism from #41566 at these lineage boundaries: every validated parent-prefix/child boundary is consecutive. Ordinal overlap occurs across real branches after the fork point, not as a duplicate within the selected linear boundary.
A supported reindex or branch-reconciliation path would also be valuable for threads that are already affected.
MeGustas-5427 commentedon Sep 7, 2026
Additional Windows report: frozen history after force-quit, plus side-chat failure
When Codex Desktop becomes stuck while a task is still running, I have to force-close the application. After reopening Codex and opening the same task, its conversation view stays at the point where it originally got stuck. Recent conversation history does not appear.
Once a task is affected, reopening that task repeatedly continues to show the same stale view. The side-chat panel also cannot be opened.
Environment
OpenAI.Codex 26.901.6511.0, verified from local Windows package metadata on September 7, 2026. This is the installed package version, not a version copied from the About dialog; the version at the original interruption was not separately recorded.Microsoft Windows NT 10.0.26100.0 x64; Windows 24H2, OS build26100.9168.Observed sequence
I do not yet have a deterministic minimal reproduction for the initial application hang. The sequence I experienced is:
Actual behavior and visible error
English translation: Failed to open side chat.
The supplied screenshot shows this toast above the affected task. The text is transcribed here so it is searchable.
Expected behavior
After an interrupted execution and app restart, reopening a task should restore all persisted conversation history and reconcile the interrupted task state. Its side chat should remain accessible.
If recovery cannot complete, the app should show an actionable recovery error instead of repeatedly presenting the stale conversation view. A supported way to recover the history of already affected tasks would also help.
Impact and scope
This prevents me from reviewing recent work and continuing reliably in an existing task after a hang.
These are observations from the UI. I have not checked whether the missing messages remain in the underlying session files, so I cannot establish actual data loss or a storage-level root cause.
I am adding this here because the force-quit followed by permanently stale history matches the visible symptoms in this issue. My additional observation is that the same affected task also fails to open its side chat. #42509 may be relevant to that symptom.
I have not confirmed whether the history and side-chat failures share an internal cause, or whether my case has the storage-level mechanism discussed above.