Repository navigation
thread/read can report stale updatedAt after replacing SQLite metadata with rollout summary #28870
Description
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/codexmain
Local evidence
Affected thread id: 019ed1ea-d71a-7bc0-92d5-06d85ce9ba74
SQLite state row:
archived = 0rollout_path = ~/.codex/sessions/2026/06/17/rollout-2026-06-17T02-31-29-019ed1ea-d71a-7bc0-92d5-06d85ce9ba74.jsonlcreated_at = 2026-06-17 02:31:29 +0700updated_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.043Zwithpayload.type = "turn_aborted"
Observed summary:
- top-level
updatedAtfrom 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_threadfirst loads SQLite metadata intostored_thread_from_sqlite_metadata(...). - Around
read_thread.rs:49-61, for no-history reads, it may replace that DB-derivedStoredThreadwithrollout_threadfromread_thread_from_rollout_path(...)when the rollout-derived preview is non-empty. - The replacement preserves
nameandgit_info, but notupdated_ator other freshness/accounting fields from SQLite.
codex-rs/rollout/src/list.rs:
- Around
list.rs:818-825,read_thread_item_from_rollout(...)callsbuild_thread_item(...)withupdated_at = None. - Around
list.rs:1075-1160,read_head_summary(...)scans only the head records and does not populatesummary.updated_at. - Around
list.rs:786-790, whensummary.updated_atis missing,build_thread_item(...)falls back to the passedupdated_at, and then tocreated_at. Sinceread_thread_item_from_rollout(...)passes no mtime fallback, single-thread rollout summaries can end up withupdatedAt = 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 anupdated_atfallback, 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. updatedAtsemantics 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
github-actions commentedon Jun 18, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
100yenadmin commentedon Jun 18, 2026
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:
b0c5a5ff5e7a47b9df9a23ed0ad57ec6985ba63dI 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 to100yenadmin/codex.What the patch changes:
updated_atexistsread_threadno-history preview merging preserves SQLite authoritativeupdated_at,recency_at, andarchived_atinstead of letting rollout fallback metadata replace themthread/read includeTurns=falseapp-server coverage asserts the wire response reports the SQLite latest update/recency metadataFocused validation run locally from
/Volumes/LEXAR/repos/codex-worktrees/thread-read-updated-at:just fmtjust test -p codex-rollout test_read_thread_item_from_rollout_uses_file_mtime --test-threads 1just test -p codex-thread-store read_thread_preserves_sqlite_freshness_when_using_rollout_preview --test-threads 1just test -p codex-rollout --test-threads 1just test -p codex-thread-store --test-threads 1just test -p codex-app-server thread_read_returns_summary_without_turns --test-threads 1git diff --checkThis also appears to address at least part of the older recents timestamp symptom in #21960.
100yenadmin commentedon Jun 18, 2026
@tibo-openai
Fengql95 commentedon Aug 2, 2026
Confirming the same stale
updatedAtbehavior on a current macOS Desktop build.Environment
26.727.51351(bundle6119)0.146.026.5.2(25F84)Sanitized reproduction
Using the same local idle thread:
list_threads(limit=50).read_threadwith the samethreadIdandhostId=local,turnLimit=1, and omitcursor.Observed:
idleupdatedAt:1785591032(2026-08-01 21:30:32 CST)idleupdatedAt:1785463264(2026-07-31 10:01:04 CST)completedcompletedAt:1785591032hasMore=truewith a realnextCursorThe list timestamp and newest-turn timestamp agree, while the task-level timestamp from
read_threadfalls 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
/feedbackreport was also recorded under feedback thread019fbfef-2c2c-7782-b873-ec2b79eaccae. No raw rollout logs, prompts, repository content, or business data are attached.dsavran commentedon Oct 14, 2026
Independently reproduced with Codex CLI 0.162.1 on macOS — Legacy vs. Paginated threads
I can confirm the reported stale
thread/read.updatedAtbehavior on Codex CLI 0.162.1, using the shared app-server daemon.I investigated six existing threads: three older threads using
Legacyhistory and three newer threads usingPaginatedhistory.Result: All three Legacy threads reproduce the bug; all three Paginated threads behave correctly.
The history formats were verified using both SQLite
history_modeand the app-server'sthread/read.historyMode, rather than inferred from thread creation dates.The following timestamps are UTC, all from 2026:
For every Legacy thread,
thread/readwithincludeTurns: falsereportsupdatedAtequal 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 thethread/readresponse 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
Updatedcolumn misleading when monitoring multiple persistent agents.