Skip to content

[macOS][0.153.4] Paginated history stalls at duplicate ordinal; controlled recovery comparison and stable backport request #43742

Open

Description

@NashCC1550

Summary

A local Codex Desktop task on macOS kept displaying early conversation history while the canonical JSONL continued to contain later user/assistant messages. The derived history projection was stuck at a duplicate ordinal.

This report adds a controlled comparison using the same bundled 0.153.4 binary and the same immutable snapshot: reopening did not recover it, rebuilding the derived rows failed again, and a narrowly guarded checkpoint correction recovered 24 turns without rewriting the canonical transcript. It also identifies the difference between the stable tag and the recovery handling merged in #42369.

Related: #43124, #43182, #43640, #41566, #41079. Please consolidate if this belongs to the same defect; the additional evidence here is the three-way recovery comparison and stable-tag behavior.

Environment

  • Desktop bundle: ChatGPT.app, CFBundleIdentifier com.openai.codex
  • Bundle version: 26.901.51231, build 8109 (read from Info.plist, not the About dialog)
  • Bundled CLI/app-server used for all experiments: codex-cli 0.153.4
  • OS: macOS 27.0, Apple Silicon arm64
  • Local task; history_mode=paginated
  • Observed September 8, 2026

Exact failure boundary, sanitized

The only ordinal discontinuity in the captured complete-line transcript prefix was:

Record Root ordinal
event_msg/token_count 272
event_msg/thread_settings_applied 272
event_msg/task_started 273

The projection expected ordinal 273 at byte offset 4,813,257, which was exactly the start of the second ordinal-272 record. That metadata record occupied 1,992 bytes. The next complete record began at offset 4,815,249 and had ordinal 273.

Runtime warning, identifiers replaced:

failed to project durable rollout for THREAD:
thread-store internal error:
thread history projection for ROLLOUT expected ordinal 273, got 272

The active rollout has a separate rollout identifier from the logical thread identifier; the stuck projection row is keyed by that rollout identifier. This distinction matters when diagnosing an edited/replaced rollout. The original action that caused ordinal reuse has not been established.

The desktop history reader returned one old turn with no next cursor while later messages remained in the canonical JSONL. Repeated application runs continued to encounter the same boundary. This is history accessibility failure, not evidence of deletion of the observed later messages.

Controlled local comparison

An immutable snapshot contained 26,925,306 bytes in the affected rollout. Separate test CODEX_HOME directories received the same target-thread state and history rows and byte-identical rollout copies. No credentials were copied, no model turn was started, and hooks were disabled in the test configuration.

Each test initialized the installed app-server, read thread/turns/list, called thread/resume with excludeTurns=true, and read thread/turns/list again.

Test Result
Existing checkpoint, reopen/resume Still 1 old turn; same expected-273/got-272 warning
Remove only this rollout's derived projection/turn/item rows in the test copy, then resume 0 projected turns; rebuild fails again at the same duplicate ordinal
Advance only this rollout's next_rollout_byte_offset past the verified duplicate metadata record, retaining expected ordinal 273 24 turns and 1,560 items recovered; no ordinal warning

The recovered snapshot included 26 userMessage and 123 agentMessage items. SQLite integrity_check returned ok. The copied canonical transcript's SHA-256 remained unchanged. After restarting the isolated app-server again, all 24 turns remained available and thread/items/list returned populated pages.

The migration CLI was also checked without --apply:

codex migrate-rollouts --thread <redacted> --json
status: already_paginated
bytes_processed: 0

It does not provide a repair path for this already-paginated thread.

Recovery on the affected installation

After backing up both state/history databases and the transcript prefix, the same narrowly scoped cursor correction was applied to one projection row, using BEGIN IMMEDIATE and old-value predicates. The expected ordinal was not changed, no history row was deleted, and no canonical record was rewritten. The existing trigger on this table is AFTER DELETE only; the operation was UPDATE.

The native live writer subsequently caught up. Read-only database checks found 24 projected turns, the checkpoint resumed advancing with new output, and the app-provided newest-first history reader returned recent turns with hasMore=true instead of the stale exhausted page. The pre-repair transcript prefix remained hash-identical. Desktop visual confirmation is tracked separately; a history API response alone is not claimed as screenshot evidence.

This is diagnostic recovery evidence for one verified metadata boundary, not a recommendation to skip arbitrary duplicate records or user/assistant content.

Stable source versus upstream fix

  • The public rust-v0.153.4 materializer still returns an error for an ordinal lower than the expected ordinal. The observed binary behavior agrees.
  • PR #42369, merged September 2, changes the mainline materializer to move past duplicate/regressed ordinals and other invalid records.
  • As of this investigation, the GitHub latest stable release is rust-v0.153.4. A mainline fix being merged does not establish that affected stable desktop installations contain it.

Please clarify the first desktop/stable runtime that includes this recovery handling and whether already-wedged checkpoints recover automatically after upgrading.

Expected fix

  1. Ship/backport the applicable safe projector recovery to the stable desktop runtime.
  2. Prevent ordinal reuse at writer/resume boundaries.
  3. Offer a supported per-thread history integrity/recovery command that handles already_paginated sessions.
  4. Show an actionable incomplete-history state when durable records remain beyond a failed checkpoint, rather than silently presenting old history as complete.
  5. Cover this token_count(N) -> thread_settings_applied(N) -> task_started(N+1) boundary, resumed-history pagination, populated message pages, and restart persistence in regression tests.

No raw transcripts, databases, prompts, credentials, private identifiers, filesystem paths, internal URLs, or full logs are attached. The writer-side initiating trigger and future recurrence on 0.153.4 remain unproven.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Sep 8, 2026
  2. Isadorah01 commented on Sep 8, 2026

    @Isadorah01

    Additional controlled recovery evidence from four affected local tasks on ChatGPT.app 26.901.51231 (build 8109), bundled codex-cli/app-server 0.153.4, macOS.

    This supports the duplicate/regressed ordinal failure described here. It is a history accessibility failure: the canonical records inspected below were still readable. We have not reproduced the initiating writer bug from a clean installation or established why the writer reused an ordinal.

    Four independently verified boundaries

    Aliases below are not real task identifiers. The first unprojected record was event_msg/thread_settings_applied in all four cases.

    Task alias Projection expected Raw ordinal at byte cursor Overlapping prior records Projected turns before -> after
    A 4540 4539 one token_count 14 -> 24
    B 1568 1566 two token_count 5 -> 8
    C (archived) 12475 12474 one token_count 18 -> 19
    D (archived) 15546 15544 two token_count 53 -> 73

    For B and D, the next record is task_started at expected-minus-one. Therefore blindly advancing the byte cursor until an ordinal matches would skip a meaningful turn-start event. The same recovery recipe is not safe for every ordinal mismatch.

    Disposable-copy comparison

    • Copied only each authorized task's rollout and required SQLite rows into separate test homes. No credentials or live configuration were copied; the OS sandbox blocked network access and access to the real Codex data directory. No model turn was started.
    • The unchanged control for A remained at 14 turns across two app-server runs.
    • In the recovery copies, kept next_rollout_byte_offset unchanged and aligned only next_rollout_ordinal with the actual record at that cursor. The native 0.153.4 projector then consumed all remaining records, including thread_settings_applied and task_started.
    • Guarded this experiment on exact immutable-prefix hashes, the old cursor pair, a regression of only one or two ordinals, overlapping prior records consisting solely of token_count, and absence of materialized items occupying those overlapping ordinals. This is narrowly scoped diagnostic evidence, not a general recommendation to bypass ordinal validation.
    • All four rollouts remained byte-identical. All 109 original final assistant responses were found in the recovered projections. All 147 canonical completed user-message texts and local-image references matched the history API.
    • Traversed all 131 native thread/items/list pages: 12,886 unique entries matched the complete recovered index. Older stored agent items omit the schema-default questions: null; only that verified nullable default was normalized for comparison.
    • All four recovered copies retained the recovered history across a second app-server restart; SQLite integrity checks passed.

    Archive and transaction handling

    Two tasks were archived. The diagnostic copies placed rollouts under test sessions/, so native resume cleared their copied archive metadata. That copy result alone does not prove preservation of live archive state.

    An archive-preserving publication transaction was separately tested: keep live state/archive metadata and canonical files unchanged, insert only missing native-generated derived turn/item rows for the archived tasks, and advance their projection checkpoints atomically. All pre-existing rows must be byte-identical to both the original snapshot and the recovered copy; no existing message is replaced and no record is deleted. Active tasks need only the guarded ordinal alignment followed by native catch-up.

    The combined disposable transaction passed. An injected second-task checkpoint conflict caused a complete rollback: all four tasks' logical table digests were unchanged.

    Live recovery outcome (after the copy tests)

    With explicit authorization, the guarded repair was applied to these four tasks. Both active tasks then caught up through the native projector; the two archived tasks received only their missing native-generated index rows. The observed turn counts are now 24, 8, 19, and 73 (34 additional turns total).

    All four checkpoints reached the sampled end of their rollout files. The original snapshot prefixes remain SHA-256 identical, and each task's original archive flag, archive timestamp, and rollout location are unchanged. Read-only SQLite integrity checks passed for both state and history databases. The desktop history API returned the previously inaccessible later turns. Rendered UI inspection was unavailable because the computer-use tool disallows controlling this application, so this is an API/index recovery result, not a claim of exhaustive frontend visual validation.

    Before applying, a fresh independent read-only review passed for this specific guarded script and evidence. A concurrent-writer test confirmed the attached state/history transaction blocks competing database writes while validating and publishing. A forced mid-transaction conflict rolled back all changes. Scoped pre-repair snapshots are retained locally; no full user database was replaced. This is not a generally safe repair recipe, and there is no automatic post-commit inverse operation.

    Request

    Please clarify which stable desktop/runtime release contains the applicable recovery handling from #42369, whether it repairs already-stalled projections, and how the writer-side ordinal reuse is being addressed. A supported per-task integrity/recovery command should preserve archived tasks, meaningful lower-ordinal events, lineage boundaries, and concurrent writes, and should surface incomplete history instead of silently showing an exhausted stale transcript.

    No raw transcripts, databases, message text, attachments, credentials, private paths, actual task identifiers, or full logs are attached. This report does not establish that 0.153.4 will remain healthy after future writer/resume transitions.

  3. ljc1002-cmd commented on Sep 10, 2026

    @ljc1002-cmd

    Additional macOS occurrence on desktop 26.903.61454 (build 8378), bundled codex-cli 0.153.4, observed September 10–11, 2026. Version read from Info.plist, not the About dialog. macOS 26.6.2 (25G83); uname -mprs: Darwin 25.6.0 arm64 arm. Local task, history_mode=paginated.

    Primary impact: lost time and broken work continuity. The task shows September 8 history while work through September 10 remains in its durable transcript. This forces the user to stop productive work and reconstruct research, decisions and execution already completed. Additional usage is secondary; the priority is restoring the original task safely and preventing recurrence.

    Read-only evidence from this installation:

    • Newest history page ends in the turn started 2026-09-08 13:46:46 KST, last visible assistant message 13:51:00.260 KST. The durable JSONL contains an assistant message at 2026-09-10 23:45:48.330 KST.
    • 439 later user/assistant messages (115 user, 324 assistant) were extracted and exactly matched against an existing recovery transcript, preserving timestamp, role, text and order. First missing user message: September 8 13:58:55.187 KST. This is the verified interval, not a claim of global completeness.
    • JSONL size: 4,673,474,499 bytes. Size is not proven causal.
    • The active continuation's projection row stores next_rollout_byte_offset=3235878886 and next_rollout_ordinal=104179. The state DB points to the same rollout examined.
    Byte offset UTC timestamp Ordinal Record
    3235877988 2026-09-08T04:52:48.465Z 104178 event_msg/token_count
    3235878886 2026-09-08T04:58:54.469Z 104178 again event_msg/thread_settings_applied
    3235880822 2026-09-08T04:58:54.476Z 104179 event_msg/task_started

    Retained logs contain 347 matching WARNs, September 10 22:45:55–23:45:49 KST, from codex_thread_store::local::live_writer:

    failed to project durable rollout for [logical-thread-id]: thread-store internal error: thread history projection for [continuation-id] expected ordinal 104179, got 104178

    The duplicate ordinal at the cursor and projection failure are verified. The original trigger for ordinal reuse is not established. No repair or synthetic reproduction was performed, and original session/database/project files were not edited. Extraction to a separate local transcript did not restore the original UI.

    Please confirm which released desktop/runtime contains the applicable recovery from #42369 and how already-stalled tasks can recover without redoing completed work or rewriting canonical transcripts. A visible incomplete-history error and a supported per-task repair path would prevent significant lost time.

    No raw transcripts, private task identifiers, credentials, absolute local paths, project content or full logs are attached.

  4. idealson-nexources commented on Sep 10, 2026

    @idealson-nexources

    Confirmed recovery of an already-stalled Desktop task using the standalone codex-cli 0.154.0, without updating or modifying Desktop and without manually editing the live SQLite projection.

    Environment: macOS 26.6.2 (25G83), Apple Silicon; Desktop 26.903.71938 (build 8576), bundled CLI/app-server 0.153.4; local task with history_mode=paginated. Recovery completed September 10, 2026.

    Failure and version distinction

    Later user/assistant messages remained in the canonical JSONL but were missing from both Desktop and terminal history. The derived history was stuck at 1,001 turns. A duplicate ordinal occurred across session_meta and event_msg/agent_message, and projection warnings reported expected ordinal N+1, got N. This is not necessarily a harmless metadata record that can safely be skipped. We have not established the initiating writer-side cause.

    Stable 0.154.0, released September 9, includes #42369. Updating the standalone CLI does not replace Desktop's bundled runtime. Importantly, in our isolated comparison, merely calling thread/read and thread/turns/list with 0.154.0 still returned the stale projection; an actual native thread/resume triggered catch-up.

    Procedure that worked in this case

    1. Validate recovery on a disposable copy first. Before live recovery, fully quit Desktop and all CLI sessions, verify no Codex writers remain, and take fresh consistent SQLite backups plus a backup/hash of the affected canonical rollout. Keep the originals and backups.

    2. Run the standalone 0.154.0 codex app-server --stdio against the normal Codex data directory. After the standard initialize handshake (with capabilities.experimentalApi: true) and initialized notification, send only:

      {"id":2,"method":"thread/resume","params":{"threadId":"<AFFECTED_THREAD_ID>","excludeTurns":true}}

      Then inspect thread/turns/list. Do not send turn/start, replay old prompts, or override the task's model, working directory or permissions. The native runtime performs its own history catch-up; no manual SQL UPDATE/DELETE, cursor manipulation, transcript rewriting, or application-bundle patching was used for the live repair.

    3. Stop the helper app-server, verify the recovered projection and original rollout-prefix hash, then reopen the unchanged Desktop application.

    Verified outcome

    • Disposable-copy test: 1,001 → 1,017 turns. A fresh process using Desktop's older bundled 0.153.4 could subsequently read the recovered history.
    • Actual recovery, after additional diagnostic exchanges: 1,001 → 1,018 turns. The previously missing sampled user/assistant exchange was present again, and the projection caught up to the captured transcript boundary.
    • SHA-256 verification confirmed the entire pre-recovery canonical transcript prefix was unchanged; native resume may append normal records.
    • After reopening Desktop, the user confirmed the missing history was visible again. No new model turn or project work was started by the recovery helper.

    This is one verified active-task recovery, not a guarantee for all history defects or archived tasks, nor proof that the older Desktop runtime cannot stall again. A supported per-task repair action, an explicit incomplete-history warning, and tests for already-stalled projections would help substantially. Please also clarify the Desktop release carrying #42369 and the separate writer-side fix for ordinal reuse.

    No raw transcripts, databases, message contents, private task identifiers, credentials, absolute local paths, or full logs are attached.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingsessionIssues involving session (thread) management, resuming, forking, naming, archiving

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions