TUI /resume can show No sessions yet when cwd-filtered thread/list drops state DB matches

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

Summary

The TUI /resume picker can display No sessions yet for the current working directory even when matching sessions exist in the local Codex state DB and can still be resumed directly by ID.

In the local repro, the same session was:

  • visible in local history/state metadata,
  • resumable with codex resume <session-id>,
  • returned by thread/list when useStateDbOnly: true was used,
  • but omitted by the default scan-and-repair thread/list path used by the TUI /resume picker with a cwd filter.

This appears related to #22037 and the lower-level issue described in #22777, but the visible CLI/TUI failure mode here is a correctness issue: the picker says there are no sessions, not only that listing is slow.

Environment

  • Codex CLI: observed on 0.139.0
  • Surface: CLI/TUI /resume
  • Platform: Linux container
  • Local state layout: CODEX_HOME is a symlink to persistent storage

The symlinked CODEX_HOME layout does not appear to be the root cause. The relevant session files and SQLite state DB were readable, and direct resume by session ID worked. I am mentioning it only because it was part of the observed environment.

Reproduction shape

  1. Have existing sessions in $CODEX_HOME/sessions whose metadata in the state DB matches a project cwd.
  2. Start codex from that project directory.
  3. Run /resume.
  4. Observe the picker shows No sessions yet for the cwd-filtered view.
  5. Run codex resume <session-id> for one of those sessions.
  6. The session resumes successfully.
  7. Compare thread/list behavior for the same cwd filter:
  • default scan-and-repair listing returns no rows for that cwd,
  • useStateDbOnly: true returns the expected rows.

No raw rollout JSONL, full local paths, auth/config files, or session contents are needed to reproduce the code-path issue.

Root cause hypothesis

RolloutRecorder::list_threads_with_db_fallback currently treats source/provider/cwd/search filters as metadata-filtered listings. When the state DB is available, it queries SQLite and can see matching rows, but for metadata-filtered listings it returns the filesystem scan page rather than the DB page.

That makes valid state DB matches disappear whenever the filesystem summary scan does not produce a ThreadItem for them. One concrete way this can happen is when a rollout has valid session_meta but the first preview-bearing user event is outside the limited head scan window. In the TUI /resume cwd-filtered path, that can surface as No sessions yet even though the state DB has the session and direct resume works.

Expected behavior

For source/provider/cwd-filtered listings, if the state DB is available and contains matching valid rows, the returned first page should include those rows after reconciliation. The filesystem scan should remain a fallback/repair path, not the authoritative returned page when SQLite has already produced a valid filtered result.

Patch experiment

I tested a small patch in a fork that changes non-search metadata-filtered listings to:

  1. run the existing filesystem scan and repair for filesystem hits,
  2. query the state DB,
  3. fully reconcile DB-only hits so stale cwd/provider/source metadata is still corrected,
  4. re-query and return the repaired DB page when SQLite is available,
  5. keep the existing filesystem fallback when SQLite is unavailable or errors.

Patch commit:

https://github.com/Moujuruo/codex/commit/93130eeb3f9bc93999fdfd20f2dbc82568d2d1fd

I kept searchTerm behavior separate because search has its own fallback semantics when the DB has not been warmed yet.

Validation run on the patch

  • just fmt
  • just test -p codex-rollout list_threads_cwd_filter_returns_state_db_only_match list_threads_default_filter_reconciles_stale_state_db_hits list_threads_search_repairs_stale_state_db_hits_before_returning
  • just test -p codex-rollout (70 passed)
  • just fix -p codex-rollout

Why this is not just a symlink problem

The local CODEX_HOME symlink was durable and readable. The same sessions could be found by ID and by state-DB-only listing. The failing path was specifically the default scan-and-repair listing used by /resume with cwd/source/provider filters returning the filesystem scan page after state DB lookup.

View original on GitHub ↗

3 Comments

github-actions[bot] contributor · 2 months ago

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

  • #28423
  • #28263

Powered by Codex Action

MakenRosa · 2 months ago

I checked this against current main, and the described failure mode lines up with the code path.

The chain I see is:

  • The TUI /resume picker builds ThreadListParams in tui/src/resume_picker.rs::thread_list_params(...) with a cwd filter, source_kinds, and use_state_db_only: false.
  • That reaches RolloutRecorder::list_threads_with_db_fallback(...) in rollout/src/recorder.rs as a metadata-filtered listing because allowed_sources, model_providers, or cwd_filters are present.
  • The filesystem side can omit a rollout even when session metadata exists: rollout/src/list.rs::build_thread_item(...) only returns an item when summary.saw_session_meta && summary.preview.is_some(). The preview scan is bounded by HEAD_RECORD_LIMIT plus USER_EVENT_SCAN_LIMIT, so a valid rollout whose first preview-bearing event is beyond that window will not become a filesystem ThreadItem.
  • After querying SQLite, the metadata-filtered branch reconciles DB-only hits, records metadata_filter, but still returns page_from_filesystem_scan(fs_page, ...) overlaid with DB metadata. That means a valid DB row that was not present in fs_page still cannot appear in the returned page.

One existing test also shows the current policy distinction: list_threads_default_filter_returns_filesystem_scan_results intentionally expects a stale DB cwd hit to disappear after reconciliation. That is a useful stale-row guard, but it does not cover the other case from this issue: a valid DB row whose rollout is readable/resumable but whose filesystem summary scan does not produce an item because preview extraction is incomplete.

The regression test I would add is specifically that second case: create a rollout with valid session_meta and a matching, non-stale state DB row, ensure the filesystem scan cannot build a preview item from the bounded head scan, then assert the default cwd/source-filtered listing returns the DB-backed row after reconciliation. That would preserve the stale-filter behavior while pinning the /resume says No sessions yet even though DB/direct resume works behavior.

Necmttn · 2 months ago

The cwd filter should run after state DB and filesystem scan results are merged.

For each candidate, keep source state_db|jsonl_scan, stored cwd, normalized cwd/project key, and filter decision. If the DB knows a session but the scan path cannot repair it, the picker should show a degraded row with the reason instead of No sessions yet.

---

_Generated with ax._