Thread rename updates session_index but leaves SQLite thread title stale
Summary
Renaming a thread updates session_index.jsonl, but it does not update threads.title in state_*.sqlite for the same thread_id.
That leaves a persistent split-brain state between the append-only session index and the SQLite metadata store.
Newer resume/list code paths can paper over this by hydrating thread.name from session_index, but the underlying drift still remains and can break older resume flows, title-based search, or any surface that relies on SQLite title as the source of truth.
Concrete drift observed
For a real saved thread:
session_index.jsonlcontained"Provision server VM"threads.titlein SQLite still contained"server vm"- the same thread id was
019c4b73-a91e-7640-9ef6-b1bf18951bb6
User-facing symptom
On affected CLI paths / older builds, codex resume 'Provision server VM' can fail with:
ERROR: No saved session found with ID Provision server VM. Run `codex resume` without an ID to choose from existing sessions.
while the same session is still resumable from the picker.
Even on newer builds where picker/list behavior is corrected by name hydration, the persisted metadata remains inconsistent.
Expected behavior
A thread rename should keep both persistence layers in sync:
- append the new name to
session_index.jsonl - update
threads.titlein SQLite for the same thread id
Why this matters
session_index is append-only and currently acts as the latest rename source, but SQLite still powers thread metadata, sorting, and some lookup/search paths. If the two stores diverge, name-based behavior becomes version-dependent and path-dependent.
Proposed fix
When append_thread_name(...) succeeds, perform a best-effort SQLite title sync for the same thread_id.
That keeps existing semantics (session_index remains append-only) while eliminating stale threads.title rows.
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Can you provide repro steps? You're describing internal details, but I'd like to understand the user-visible behaviors and the steps needed to repro the problem.
I can reproduce this on Codex Desktop for macOS, and it also has a visible sidebar/user-facing impact when the first prompt is long.
Environment:
User-visible symptom:
/Users/<user>/Desktop/.../some-document.docx.Concrete local drift observed after restart:
I also observed a successful Desktop app-server rename request in the app log:
So the app/server path for rename is being exercised, but the SQLite
threads.titlerows remain stale whilesession_index.jsonlcarries the later concise names. In one case,session_index.jsonlalso has multiple entries for the same thread id, with the later entry being the desired short name.Repro steps from the Desktop side:
~/.codex/session_index.jsonllatestthread_namefor that id~/.codex/state_5.sqlitethreads.titlefor the same idExpected:
session_index.jsonlandstate_5.sqlite.threads.titleconsistent.This is especially noticeable on Desktop because long path-derived titles occupy the whole sidebar row and make project history difficult to navigate.
Yes — here is a repro-oriented version focused on the user-visible behavior rather than the internal diagnosis.
User-visible repro shape
The easiest way to see the problem is to start from a thread whose first/title text is short or stale, then let a later explicit/generated rename give it a different, more useful name.
Steps
``
text
``server vm
``
text
``Provision server VM
This can be through the normal rename/title-generation path that writes the new thread name into the session index.
``
bash
``codex resume 'Provision server VM'
```bash
rg '019c4b73-a91e-7640-9ef6-b1bf18951bb6|Provision server VM|server vm' ~/.codex/session_index.jsonl
sqlite3 ~/.codex/state_5.sqlite \
"select id, title, first_user_message from threads where id = '019c4b73-a91e-7640-9ef6-b1bf18951bb6';"
```
Replace the id with the new thread id if reproducing from scratch.
Actual behavior observed
For the concrete thread I reported:
019c4b73-a91e-7640-9ef6-b1bf18951bb6session_index.jsonlname:Provision server VMthreads.title:server vmOn affected CLI paths / older builds, this can make the new visible title fail as a direct resume key:
while the same session can still be found from picker-style flows that hydrate or merge names differently.
Expected behavior
After a thread rename, the same thread id should have the same effective title in both persistence layers:
session_index.jsonlrecords the new name, andthreads.titleis updated/best-effort synced to the same new name.That way title-based resume/search/list behavior does not depend on which persistence layer a given surface uses.
Why this is user-visible
The visible symptom is not the database mismatch itself; it is that the user sees or expects the renamed title, but title-based lookup/search can still behave as if the old title is authoritative. On Desktop/sidebar surfaces this also shows up as stale or long first-prompt titles continuing to occupy the row even after a concise generated/renamed title exists elsewhere.
Blitz
$code-reviewupdate on current mainChecked against current
openai/codexmain:89ac3ec27cae0000da14fbe4160a79ed465843fb(89ac3ec27cae)Short version: this looks partially fixed, but not fully closed.
What appears fixed now:
codex-rs/thread-store/src/local/update_thread_metadata.rs:502-516state_db.update_thread_title(thread_id, &name)beforeappend_thread_name(...).Remaining issues found in the blitz review:
session_indexnamescodex-rs/thread-store/src/local/read_thread.rs:304stored_thread_from_sqlite_metadata()trusts any distinct SQLitethreads.titleand only consultssession_indexwhen the SQLite title is missing/non-distinct.Related paths:
codex-rs/thread-store/src/local/list_threads.rs:83codex-rs/thread-store/src/local/search_threads.rs:198codex-rs/tui/src/lib.rs:697So a pre-existing split-brain row such as SQLite title
server vm+ latestsession_indexnameProvision server VMcan still surface the old SQLite title on read/list/search-style paths unless the stale row is repaired or latestsession_indexis preferred.codex-rs/rollout/src/state_db.rs:515Reconcile calls
metadata.prefer_existing_explicit_title(&existing_metadata), so an existing stale-but-distinct SQLite title is preserved rather than repaired from the latestsession_indexname.codex-rs/thread-store/src/local/update_thread_metadata.rs:245ThreadMetadataPatch::titleis documented as a best-effort title derived from history, but it is applied unconditionally:``
rust
``if let Some(title) = patch.title {
metadata.title = title;
}
That can recreate the split-brain state: SQLite gets a later derived title while
session_indexstill has the explicit user-facing name. The current testmetadata_patch_applies_title_over_existing_nameappears to encode this behavior.Suggested minimal fix shape:
patch.titleunlesspatch.nameis also present.session_indexname for the thread and either prefer it or backfillthreads.titlewhen it differs from stale SQLite.session_indexname = new, then read/list/search/resume surfaces the new name and does not let the stale SQLite title win.Net: the original future-rename write path seems fixed, but legacy drift/backfill and derived-title overwrite cases still make this issue relevant.
Additional reproduction: Windows Desktop + WSL with a CODEX_HOME transition
I can reproduce the user-visible title divergence on Codex Desktop for Windows when the selected agent is WSL and the Desktop WSL app-server's
CODEX_HOMEchanges.Environment:
CODEX_HOME: Windows-mounted directoryCODEX_HOME: WSL-native directorySanitized observations for one task ID:
CODEX_HOMEpointed to the Windows-mounted home.session_index.jsonlhas two entries for the same task ID:CODEX_HOMEand restarting Desktop, the new home'ssession_index.jsonlhas no entry for that task ID.Result: the same task ID appears with two different titles depending on which persistence surface/home is used:
This confirms the issue remains user-visible in the current build. The ordinary future rename path may now update both stores when they share one home, but an existing task discovered across a
CODEX_HOMEtransition still does not hydrate or reconcile its latest explicit name from the rollout's owning home.Expected behavior:
This is adjacent to #33815, where the same cross-home discovery works for reading but archive routing fails. No usernames, task IDs, project paths, or transcript content are included here.