openai / openai/codex

Codex Desktop: chat history stops rendering after crash mid-turn (projection cursor never follows rollout file rotation)

Open
#42,387 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

app bug session windows-os
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

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):

  1. codex-rs/thread-store/src/local/live_writer.rs — the rollout log path for a thread's live writer is resolved once, at resume_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.
  2. codex-rs/thread-store/src/local/thread_history_materialization.rsread_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.
  3. codex-rs/thread-store/src/local/thread_history.rsapply_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>.jsonlrollout-<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?

  1. Start a Codex Desktop session and have a multi-turn conversation.
  2. 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 / interrupted in the rollout log.
  3. 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).
  4. 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.
  5. (Diagnostic, optional) On a copy of ~/.codex/thread_history_1.sqlite, query:
    SELECT next_rollout_byte_offset, next_rollout_ordinal
    FROM thread_history_projection_state WHERE thread_id = '<affected thread id>';
    
    and compare against the byte size of the pre-crash rollout file under ~/.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 .jsonl log 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.

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 with codex-rs/thread-store/src/local/live_writer.rs, thread_history_materialization.rs, and thread_history.rs, tracing resume_thread, read_projection_steps(), and apply_projection(). Reproduce an interrupted-turn resume and compare the projection cursor with the old and new rollout files. Done means the SQLite thread_items/thread_turns projection continues advancing after rotation and the rendered history includes post-crash messages.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, sqlite
Domain
databases, desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
67/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.