Thread rename updates session_index but leaves SQLite thread title stale

Open 💬 6 comments Opened Apr 1, 2026 by iqdoctor
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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.jsonl contained "Provision server VM"
  • threads.title in 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.title in 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.

View original on GitHub ↗

6 Comments

github-actions[bot] contributor · 3 months ago

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

  • #16280
  • #16066
  • #16049
  • #15943

Powered by Codex Action

etraut-openai contributor · 3 months ago

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.

NightGlowww · 1 month ago

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:

  • Codex Desktop: 26.519.31651, bundle version 3017
  • bundled / CLI version: codex-cli 0.133.0
  • macOS: 26.5 build 25F71, x86_64

User-visible symptom:

  • A thread that starts with a long absolute file path / long task prompt can continue to show that long first prompt as the chat title in the Desktop Projects sidebar, even after a concise generated/renamed title exists elsewhere.
  • This makes the Projects list hard to scan because real work often starts with paths like /Users/<user>/Desktop/.../some-document.docx.

Concrete local drift observed after restart:

Thread A
state_5.sqlite threads.title:
  long first prompt beginning with an absolute file path and document-editing instructions
session_index.jsonl:
  older entry: long absolute path title
  later entry: "Course material revision"

Thread B
state_5.sqlite threads.title:
  long first prompt beginning with an unzip/package path and task details
session_index.jsonl latest thread_name:
  "Homepage design and VIP features"

Thread C
state_5.sqlite threads.title:
  first prompt about adding an email module
session_index.jsonl latest thread_name:
  "Email module development"

I also observed a successful Desktop app-server rename request in the app log:

method=thread/name/set ... conversationId=<thread-id> ... errorCode=null

So the app/server path for rename is being exercised, but the SQLite threads.title rows remain stale while session_index.jsonl carries the later concise names. In one case, session_index.jsonl also has multiple entries for the same thread id, with the later entry being the desired short name.

Repro steps from the Desktop side:

  1. Open Codex Desktop on macOS.
  2. Start or resume a local thread whose first user message begins with a long absolute path and detailed task text.
  3. Let the app generate or set a concise thread name later.
  4. Restart Codex Desktop.
  5. Compare:
  • ~/.codex/session_index.jsonl latest thread_name for that id
  • ~/.codex/state_5.sqlite threads.title for the same id
  • the title shown in Desktop Projects/sidebar surfaces

Expected:

  • Rename/title generation should keep session_index.jsonl and state_5.sqlite.threads.title consistent.
  • Desktop sidebar/list surfaces should prefer the latest explicit/generated thread name, not the stale first prompt or path.

This is especially noticeable on Desktop because long path-derived titles occupy the whole sidebar row and make project history difficult to navigate.

iqdoctor · 1 month ago

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

  1. Start Codex and create a new thread with an initial prompt that will produce an initial title, for example:

``text
server vm
``

  1. Let the thread be saved normally.
  1. Rename the same thread to a different title, for example:

``text
Provision server VM
``

This can be through the normal rename/title-generation path that writes the new thread name into the session index.

  1. Exit/restart Codex so future resume/list/search paths read persisted metadata rather than only live in-memory state.
  1. Try to resume/search by the new visible title:

``bash
codex resume 'Provision server VM'
``

  1. Compare the persisted metadata for the same thread id:

```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:

  • thread id: 019c4b73-a91e-7640-9ef6-b1bf18951bb6
  • latest session_index.jsonl name: Provision server VM
  • SQLite threads.title: server vm

On affected CLI paths / older builds, this can make the new visible title fail as a direct resume key:

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 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:

  • append-only session_index.jsonl records the new name, and
  • SQLite threads.title is 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.

iqdoctor · 1 month ago

Blitz $code-review update on current main

Checked against current openai/codex main:

  • Commit reviewed: 89ac3ec27cae0000da14fbe4160a79ed465843fb (89ac3ec27cae)
  • Scope: rename/thread-name/session_index/SQLite title paths only
  • Diff state: clean local checkout

Short version: this looks partially fixed, but not fully closed.

What appears fixed now:

  • The ordinary future rename path now writes both stores:
  • codex-rs/thread-store/src/local/update_thread_metadata.rs:502-516
  • it calls state_db.update_thread_title(thread_id, &name) before append_thread_name(...).

Remaining issues found in the blitz review:

  1. [MEDIUM] Existing stale SQLite titles can still mask newer session_index names

codex-rs/thread-store/src/local/read_thread.rs:304
stored_thread_from_sqlite_metadata() trusts any distinct SQLite threads.title and only consults session_index when the SQLite title is missing/non-distinct.
Related paths:

  • codex-rs/thread-store/src/local/list_threads.rs:83
  • codex-rs/thread-store/src/local/search_threads.rs:198
  • codex-rs/tui/src/lib.rs:697

So a pre-existing split-brain row such as SQLite title server vm + latest session_index name Provision server VM can still surface the old SQLite title on read/list/search-style paths unless the stale row is repaired or latest session_index is preferred.

  1. [MEDIUM] State DB reconcile preserves stale explicit SQLite titles

codex-rs/rollout/src/state_db.rs:515
Reconcile calls metadata.prefer_existing_explicit_title(&existing_metadata), so an existing stale-but-distinct SQLite title is preserved rather than repaired from the latest session_index name.

  1. [MEDIUM] Derived title patches can still overwrite an explicit name in SQLite

codex-rs/thread-store/src/local/update_thread_metadata.rs:245
ThreadMetadataPatch::title is 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_index still has the explicit user-facing name. The current test metadata_patch_applies_title_over_existing_name appears to encode this behavior.

Suggested minimal fix shape:

  • Preserve an existing explicit/user-set name when applying derived patch.title unless patch.name is also present.
  • During read/list/search/reconcile, compare the latest session_index name for the thread and either prefer it or backfill threads.title when it differs from stale SQLite.
  • Add regression coverage for: SQLite title = old, latest session_index name = 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.

solankydev · 3 days ago

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_HOME changes.

Environment:

  • Codex Desktop: 26.715.2305.0
  • Bundled WSL runtime recorded by the task: 0.145.0-alpha.18
  • Runtime: WSL2
  • Original CODEX_HOME: Windows-mounted directory
  • Current CODEX_HOME: WSL-native directory
  • SQLite home remains WSL-native/shared

Sanitized observations for one task ID:

  1. The task was created by the WSL agent while CODEX_HOME pointed to the Windows-mounted home.
  2. The original home's session_index.jsonl has two entries for the same task ID:
  • the initial first-prompt-derived title
  • a later concise generated/renamed title
  1. After changing the WSL app-server to the WSL-native CODEX_HOME and restarting Desktop, the new home's session_index.jsonl has no entry for that task ID.
  2. Desktop/shared SQLite can still resolve and continue the same task ID.
  3. A direct thread read on the current WSL host returns no explicit title and falls back to the initial-message preview.
  4. The Windows-side metadata surface continues to show the later concise title.

Result: the same task ID appears with two different titles depending on which persistence surface/home is used:

Windows-side session index: latest concise renamed title
Current WSL/shared-state read: initial-message preview fallback

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_HOME transition still does not hydrate or reconcile its latest explicit name from the rollout's owning home.

Expected behavior:

  • One task ID should expose one effective latest title across Desktop/WSL surfaces.
  • When cross-home task discovery is supported, title lookup should either:
  • read the latest explicit name from the rollout's owning Codex home,
  • migrate/reconcile that name into shared SQLite/current state, or
  • preserve an owning-home reference in task metadata.

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.