Skip to content

thread/read can report stale updatedAt after replacing SQLite metadata with rollout summary #28870

Open

Description

@100yenadmin

Summary

thread/read / thread summary paths can report a stale updatedAt for rollout-backed threads. In a local affected thread, the state DB and rollout file both showed later activity through an aborted turn, but the thread summary reported updatedAt equal to the original creation timestamp.

This looks like a metadata projection bug where fresh SQLite thread metadata can be replaced by a rollout-head summary that does not know the file's modified time or latest event timestamp.

Environment

  • Codex Desktop: 26.611.62324
  • Bundle version: 4028
  • Source checkout inspected: openai/codex main

Local evidence

Affected thread id: 019ed1ea-d71a-7bc0-92d5-06d85ce9ba74

SQLite state row:

  • archived = 0
  • rollout_path = ~/.codex/sessions/2026/06/17/rollout-2026-06-17T02-31-29-019ed1ea-d71a-7bc0-92d5-06d85ce9ba74.jsonl
  • created_at = 2026-06-17 02:31:29 +0700
  • updated_at = 2026-06-18 01:14:39 +0700

Filesystem:

  • rollout mtime: 2026-06-18 01:14:39 +0700
  • last rollout record: 2026-06-17T18:14:39.043Z with payload.type = "turn_aborted"

Observed summary:

  • top-level updatedAt from a read-thread summary was equal to the creation timestamp rather than the DB/file/latest-event timestamp.

Relevant source pointers

codex-rs/thread-store/src/local/read_thread.rs:

  • Around read_thread.rs:33-48, read_thread first loads SQLite metadata into stored_thread_from_sqlite_metadata(...).
  • Around read_thread.rs:49-61, for no-history reads, it may replace that DB-derived StoredThread with rollout_thread from read_thread_from_rollout_path(...) when the rollout-derived preview is non-empty.
  • The replacement preserves name and git_info, but not updated_at or other freshness/accounting fields from SQLite.

codex-rs/rollout/src/list.rs:

  • Around list.rs:818-825, read_thread_item_from_rollout(...) calls build_thread_item(...) with updated_at = None.
  • Around list.rs:1075-1160, read_head_summary(...) scans only the head records and does not populate summary.updated_at.
  • Around list.rs:786-790, when summary.updated_at is missing, build_thread_item(...) falls back to the passed updated_at, and then to created_at. Since read_thread_item_from_rollout(...) passes no mtime fallback, single-thread rollout summaries can end up with updatedAt = createdAt.

codex-rs/rollout/src/recorder.rs:

  • Around recorder.rs:1010-1067, fill_missing_thread_item_metadata(...) only fills missing fields from state metadata; it does not override stale rollout-derived fields.

Expected behavior

Thread summary metadata should not regress from DB/file freshness to creation time.

At minimum:

  • read_thread_from_rollout_path(...) / read_thread_item_from_rollout(...) should use file mtime as an updated_at fallback, as listing-by-updated-time already does.
  • If SQLite has a fresher updated_at, the no-history read path should preserve it when replacing or merging rollout-derived preview fields.
  • updatedAt semantics should be consistent across thread list, thread read, and Desktop sidebar/detail views.

Why this matters

This bug may be separate from the Desktop lockup tracked in #28867, but it was found while debugging the same class of "old active threads do not reactivate correctly after update" reports. If Desktop relies on stale summary timestamps/status while separately reconstructing recent interrupted turns, it can contribute to confusing thread ordering, stale display state, or incorrect resume/reactivation behavior.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    on Jun 18, 2026
  2. github-actions commented on Jun 18, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  3. 100yenadmin commented on Jun 18, 2026

    @100yenadmin
    Author

    Patch branch published for this concrete metadata bug:

    Compare: https://github.com/openai/codex/compare/main...100yenadmin:codex/thread-read-updated-at?expand=1
    Commit: b0c5a5ff5e7a47b9df9a23ed0ad57ec6985ba63d

    I could not open the upstream PR directly because GitHub returned GraphQL: 100yenadmin does not have the correct permissions to execute CreatePullRequest, but the branch is pushed to 100yenadmin/codex.

    What the patch changes:

    • rollout direct reads now fall back to the rollout file mtime when no summary updated_at exists
    • thread-store read_thread no-history preview merging preserves SQLite authoritative updated_at, recency_at, and archived_at instead of letting rollout fallback metadata replace them
    • thread/read includeTurns=false app-server coverage asserts the wire response reports the SQLite latest update/recency metadata

    Focused validation run locally from /Volumes/LEXAR/repos/codex-worktrees/thread-read-updated-at:

    • just fmt
    • just test -p codex-rollout test_read_thread_item_from_rollout_uses_file_mtime --test-threads 1
    • just test -p codex-thread-store read_thread_preserves_sqlite_freshness_when_using_rollout_preview --test-threads 1
    • just test -p codex-rollout --test-threads 1
    • just test -p codex-thread-store --test-threads 1
    • just test -p codex-app-server thread_read_returns_summary_without_turns --test-threads 1
    • git diff --check

    This also appears to address at least part of the older recents timestamp symptom in #21960.

  4. 100yenadmin commented on Jun 18, 2026

    @100yenadmin
    Author
  5. Fengql95 commented on Aug 2, 2026

    @Fengql95

    Confirming the same stale updatedAt behavior on a current macOS Desktop build.

    Environment

    • Codex Desktop / ChatGPT app: 26.727.51351 (bundle 6119)
    • Codex CLI used for the feedback flow: 0.146.0
    • macOS: 26.5.2 (25F84)
    • Observation timezone: Asia/Shanghai

    Sanitized reproduction

    Using the same local idle thread:

    1. Call list_threads(limit=50).
    2. Call read_thread with the same threadId and hostId=local, turnLimit=1, and omit cursor.
    3. Compare task-level timestamps with the newest turn timestamp.

    Observed:

    • list status: idle
    • list updatedAt: 1785591032 (2026-08-01 21:30:32 CST)
    • read status: idle
    • read task-level updatedAt: 1785463264 (2026-07-31 10:01:04 CST)
    • newest turn status: completed
    • newest turn completedAt: 1785591032
    • hasMore=true with a real nextCursor
    • no unavailable hosts or sources

    The list timestamp and newest-turn timestamp agree, while the task-level timestamp from read_thread falls back to the old value.

    This reproduced after two full Desktop restarts. Both APIs returned quickly after restart, so the remaining problem appears to be metadata projection/merge consistency rather than connectivity.

    A no-logs /feedback report was also recorded under feedback thread 019fbfef-2c2c-7782-b873-ec2b79eaccae. No raw rollout logs, prompts, repository content, or business data are attached.

  6. dsavran commented on Oct 14, 2026

    @dsavran

    Independently reproduced with Codex CLI 0.162.1 on macOS — Legacy vs. Paginated threads

    I can confirm the reported stale thread/read.updatedAt behavior on Codex CLI 0.162.1, using the shared app-server daemon.

    I investigated six existing threads: three older threads using Legacy history and three newer threads using Paginated history.

    Result: All three Legacy threads reproduce the bug; all three Paginated threads behave correctly.

    The history formats were verified using both SQLite history_mode and the app-server's thread/read.historyMode, rather than inferred from thread creation dates.

    The following timestamps are UTC, all from 2026:

    Thread History mode Created SQLite updated_at thread/read.updatedAt Rollout mtime
    L1 Legacy Jul 31 12:26:16 Oct 14 11:27:36 Jul 31 12:26:16 Oct 14 11:27:36
    L2 Legacy Aug 21 09:16:55 Oct 14 21:39:17 Aug 21 09:16:55 Oct 14 21:39:17
    L3 Legacy Aug 24 22:14:24 Oct 14 22:29:01 Aug 24 22:14:24 Oct 14 22:29:03
    P1 Paginated Aug 31 22:03:47 Oct 14 21:51:59 Oct 14 21:51:59 Oct 14 21:51:59
    P2 Paginated Sep 28 09:19:16 Oct 14 21:42:28 Oct 14 21:42:28 Oct 14 21:42:28
    P3 Paginated Oct 09 17:26:35 Oct 14 21:44:08 Oct 14 21:44:08 Oct 14 21:44:08

    For every Legacy thread, thread/read with includeTurns: false reports updatedAt equal to the original creation timestamp, despite recent activity being confirmed by SQLite and the rollout file.

    For every Paginated control thread, the same API returns the correct recent timestamp.

    This also matches a visible symptom in the Codex CLI Agent Command Center: older Legacy threads display ages such as 50d ago, even after new turns, while newer Paginated threads show recent updates. The precise UI data flow was not separately instrumented, so the connection to the thread/read response is strongly suggested but not independently established.

    The findings are consistent with the code paths identified in this issue: the Legacy no-history read can replace SQLite-derived metadata with a rollout-head summary without preserving updated_at, and the direct rollout read lacks a file-mtime fallback.

    Impact: Long-lived Legacy threads appear inactive or outdated despite successful recent work, making the Updated column misleading when monitoring multiple persistent agents.

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