Skip to content

macOS: newest-first thread lookup returns two-day-old in-progress turn despite newer persisted messages #43129

Open

Description

@iDEAnyc

Recent conversation lookup returns two-day-old turns while newer messages remain in the local transcript

Environment

  • Observed September 5, 2026, around 8:33 PM America/New_York (September 6, 00:33 UTC).
  • macOS 26.5.2, arm64.
  • Long-running local Codex conversation spanning multiple days.
  • Installed desktop/build version was not established. The documented compatibility bundle path was absent; no version is inferred.

User-visible symptom

The user reports that substantial recent conversation history disappeared from the displayed chat and that the conversation appears to have reverted approximately two days. The UI symptom is user-reported; the retrieval mismatch below was independently observed.

Independently verified evidence

  1. Read the affected thread using the available read_thread app tool with includeOutputs: false, without a pagination cursor.
  2. A request for the newest eight turns returned old September 3 work. Repeating with turnLimit: 2 returned the same old newest turn.
  3. The second response declared page.order: newest_first, page.limit: 2, and hasMore: true.
  4. Its newest returned turn had startedAt: 1788468606 (September 3, 2026, 20:50:06 UTC), status: inProgress, and completedAt: null.
  5. The thread metadata was current: updatedAt: 1788654804 (September 6, 2026, 00:33:24 UTC), despite returning a September 3 turn as newest.
  6. A read-only inspection of the matching local session JSONL file found newer user/assistant message records dated September 6 at 00:32:32, 00:33:06 and 00:33:08 UTC, including the user's current report. The file was approximately 147 MiB at inspection.
  7. Independently, the working repository retained the latest same-evening committed work. This is not evidence of a repository rollback.

Expected behavior

The current conversation view and a cursor-free newest-first thread lookup should expose the latest persisted messages, with consistent turn status and ordering. Pagination should not silently make a multi-day-old turn appear to be the latest available work.

Actual behavior and impact

The newest-first lookup is inconsistent with current thread metadata and recent persisted messages. The visible chat reportedly loses approximately two days of history. This undermines confidence in completed work and could cause an operator or agent to repeat already completed actions based on stale history.

Reproduction limits / hypotheses

  • Confirmed twice against one affected existing thread; no minimal synthetic reproduction yet.
  • Recent sampled messages are present on disk. Completeness of every intervening message has not been audited, so this report does not claim universal absence of data loss.
  • A stale index, incorrect pagination boundary, or turn reconstruction problem is a working hypothesis, not a proven cause.
  • No cache deletion, transcript rewrite, database mutation, restart, or recovery action was performed during diagnosis.

Requested investigation

Please investigate how the desktop conversation view and read_thread newest-first pagination reconstruct long-running turns from persisted session records, particularly when an old turn remains marked in progress despite newer records and current thread metadata.

Related reports

Privacy

No raw transcript, screenshots, log attachments, conversation text, thread/session identifiers, project/repository names, hostnames, local account paths, credentials, tokens, URLs from captured activity, or production data are included. This report contains only a manually selected symptom description, software environment and diagnostic timing/status metadata.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    on Sep 6, 2026
  2. argszero commented on Sep 6, 2026

    @argszero

    Hi — this looks like the same underlying thread-history projection family as #43124 (which I just root-caused on main). In both, the durable SQLite projection (thread_history_projection_state) has stalled behind the intact append-only transcript, so reads return an old turn even though newer messages exist in the rollout.

    The code mechanism is at codex-rs/thread-store/src/local/thread_history.rs:144-149: apply_projection raises expected ordinal {next_ordinal}, got {ordinal} when the stored next_rollout_ordinal no longer matches the next line's ordinal at the stored byte checkpoint. Once the projection state is transactionally half-advanced, migrate-rollouts returns already_paginated (rollout_migration.rs:513-517) without checking projection completeness — so there is no repair path and the stall is permanent.

    Linking these reports helps the maintainers see the full repro range (different clients, same projection-stall signature). I posted a detailed diagnostic + two suggested fix directions on #43124; if you can confirm the next_rollout_byte_offset/next_rollout_ordinal values and the rollout byte at that offset, it would pin the divergence cause in both.

  3. killersteps commented on Sep 6, 2026

    @killersteps

    Adding an independent reproduction: in a long-running local conversation, read_thread repeatedly returns an old in-progress turn as the newest turn, even though the raw session JSONL contains multiple subsequently completed turns and the current user message.

    Environment

    • Installed desktop package: 26.901.51231, build 8109, read from the package's Info.plist.
    • Affected session's session_meta.cli_version: 0.153.4.
    • Platform: Darwin 25.6.0 arm64 arm.
    • Most recent reproduction during this investigation: 2026-09-06T02:54:11Z.

    Independently verified observations

    Repeated calls to the desktop-provided mcp__codex_app__read_thread tool, without a pagination cursor, returned the same old turn as newest. This occurred with both turnLimit: 2 and turnLimit: 3.

    The most recent request was as follows, with the thread ID redacted:

    {
      "threadId": "<affected-thread>",
      "turnLimit": 2,
      "includeOutputs": false,
      "maxOutputCharsPerItem": 200
    }

    The relevant response fields are summarized below. newestReturnedTurn is a descriptive wrapper for the first returned turn, and itemCount is derived from its item array. The thread metadata was current at query time, but the turn presented as newest had started approximately six hours earlier:

    {
      "thread": { "updatedAt": 1788663251 },
      "page": { "order": "newest_first", "limit": 2, "hasMore": true },
      "newestReturnedTurn": {
        "status": "inProgress",
        "startedAt": 1788641639,
        "completedAt": null,
        "itemCount": 4
      }
    }

    That startedAt corresponds to 2026-09-05T20:53:59Z. A read-only scan of the raw session JSONL for the same thread confirmed that these later records exist:

    UTC timestamp Persisted record
    2026-09-05T22:55:47.347Z A subsequent turn's task_complete, with a non-empty last_agent_message
    2026-09-06T02:32:10.295Z A later turn's task_complete, with a non-empty last_agent_message
    2026-09-06T02:44:10.972Z Another later turn's task_complete, with a non-empty last_agent_message
    2026-09-06T02:52:13.112Z The current user's response_item message

    These timestamps come from actual event or message records, not file modification times or historical text quoted inside tool output. The raw records and tool requests refer to the same thread; its identifiers and conversation content are withheld for privacy.

    Impact and expected behavior

    The conversation appears to have reverted to an earlier stage of work. An agent relying on this history can incorrectly conclude that completed work is unfinished, overlook subsequent requirements, or repeat completed operations.

    A cursor-free newest-first history request should expose the latest persisted turns. If history reconstruction is incomplete, the tool should return an explicit error or incomplete-state marker rather than silently return stale turns as a successful result.

    Diagnostic limits

    • This was reproduced repeatedly against an existing affected thread. There is no minimal reproduction starting from an empty conversation yet.
    • The later records listed above were confirmed present; this does not establish the completeness of every message in the entire conversation.
    • Only the desktop read_thread tool was directly tested. The underlying thread/read and thread/turns/list methods were not called directly.
    • This investigation did not inspect the affected thread's SQLite projection ordinals and does not establish the mechanism described in macOS desktop history freezes at older turns: projection expected ordinal 3185, got 3184; migration says already_paginated #43124 as this case's root cause.
    • No session files, databases, or caches were modified or deleted, and no rebuild or recovery commands were run. Diagnosis was read-only.
    • This report includes only selected version information, call parameters, and timing/status metadata. It contains no raw log attachments, conversation content, actual thread identifiers, project names, local paths, or credentials.
  4. Stahl-G commented on Sep 7, 2026

    @Stahl-G

    I am also encountering this issue in a long-running Codex desktop conversation.

    The app's read_thread tool, called without a cursor and requesting the newest turns, returns an older in-progress turn rather than the latest messages. Repeating the lookup with a smaller turn limit returns the same stale turn. Newer messages are still present in the local transcript, so the lookup does not reflect the latest persisted conversation.

    General reproduction steps:

    1. Continue a long-running desktop conversation over multiple sessions.
    2. Send additional messages later in the same conversation.
    3. Ask the agent to read the conversation's most recent turns using read_thread, without a pagination cursor.
    4. Compare the returned turns with the recent conversation.

    Expected: the newest-first lookup returns the most recent persisted messages.
    Actual: it can return an old turn as the newest, giving the impression that recent history has disappeared.

    I have not established a minimal synthetic reproduction or root cause. No private transcript, identifiers, timestamps, file paths, storage metadata, or task data are included in this report.

  5. kernelpanic888 commented on Sep 14, 2026

    @kernelpanic888

    Issue: #43129.

    Minimal projection-lag fault injection, not a reproduction of the original writer failure

    I tested official app-server 0.154.0 on macOS 26.5.1 arm64 through stdio, with synthetic files only. This narrows the public read/recovery behavior; it does not invoke the closed desktop read_thread wrapper or establish how the original inconsistent state arose.

    Procedure:

    1. Create two tiny legacy histories. The control contains an older unfinished turn and a newer completed turn; the other contains only the older unfinished turn.
    2. Run the official migrate-rollouts --apply --json --thread <synthetic-id> in the isolated test home. Both migrations report migrated; the complete control returns its newer completed turn.
    3. After closing the test server, append four valid ordinalized records for a newer turn to the prefix fixture: start, completed user item, completed assistant item, completion. Do not change SQLite. This deliberately creates durable-tail / projection lag.
    4. Start a fresh server and call thread/read(includeTurns:true) and thread/turns/list(limit:2,sortDirection:desc,itemsView:full).
    5. Repeat official migration for the affected synthetic ID, restart and read again.
    6. Finally resume that synthetic thread, without starting any model turn, then restart and read. This validates the exact appended records through the product's materialization path.
    Stage Durable bytes Projection checkpoint Observed result
    Migrated prefix 1071 byte 1071 / ordinal 3 Older turn only
    After four-row append and fresh-process reads 2192 byte 1071 / ordinal 3 Older turn only; successful response, no stale/incomplete marker
    After migration retry 2192 byte 1071 / ordinal 3 already_paginated, zero bytes processed; still older turn only
    After synthetic resume + restart Product writes may extend the fixture Materialization catches up Newer completed turn with both items returned

    The stored older turn is interrupted here, whereas the reported desktop case was inProgress. This is an intentional fault-injection example of stale-success behavior, not an exact reproduction of all UI symptoms. A separate normal legacy append/partial-line control does expose the latest turn after append and after restart.

    Source connection and remaining question

    The release's paginated list_turns validates metadata and reads projected rows, without materializing the unread durable tail at that entry point. The existing already-paginated migration branch is not a general projection-repair operation. This explains the observed fixture; it does not identify the original failure that left a projection behind.

    Current materialization includes handling for gaps/duplicate ordinals, so the earlier ordinal-mismatch theory must be checked against the affected binary, not assumed unchanged. No production data or projection table was edited in this investigation.

    Suggested regression/design question: when a paginated read knows its durable tail is ahead of its projection, should it perform a bounded catch-up under the appropriate locks, return an explicit incomplete/recovering state, or expose a safe repair operation? Returning ordinary success conceals this distinction. Avoid an unconditional full-log scan: the related large-history reports show why that would be risky.

    Reproducer: app-server-harness.mjs and projection-lag-repro.mjs. Run node projection-lag-repro.mjs beside the official macOS-arm64 binary. The migration/resume operations are restricted to generated fixtures. This is not a recommendation to run repair commands on personal conversations, nor proof that resume repairs the original reported failure.

    Alexey Salkutsan selected the investigation. Lina, an AI assistant, authored and ran the harness and prepared this analysis. The previous reporters' observations and hypotheses remain theirs.

    Reproducer package

    Download the synthetic reproductions and reader audit. The same small packet supports this issue and the related investigations in #43017, #43129 and #38611. It contains no personal histories, databases, raw RPC logs or bundled executable. Original third-party reader code is fetched by the setup with a pinned hash and attribution.

    Verified after extraction into a clean directory on macOS arm64 with Node 24.19.0. Read README_EN.md, then run node bootstrap.mjs and node verify.mjs --portable; setup downloads pinned public dependencies and the official binary. The clean verification used the already downloaded, hash-verified official binary archive with --archive; it fetched the reader and npm dependencies afresh. Linux, Windows and the uncached binary-download branch were not tested. The ZIP contains the earlier preparation drafts, whose NOT SENT headings describe the pre-publication snapshot.

    ZIP SHA-256: 2d60212455f72dddfd52410b3c8cc55e826ba2900e0813e26c070d65566972fb.

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 appapp-serverIssues involving app server protocol or interfacesbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions