Skip to content

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

Open

Description

@Moujuruo

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:

Moujuruo@93130ee

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.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    TUIIssues related to the terminal user interface: text input, menus and dialogs, and terminal display
    app-serverIssues involving app server protocol or interfaces
    on Jun 16, 2026
  2. github-actions commented on Jun 16, 2026

    @github-actions
    Contributor
  3. MakenRosa commented on Jun 16, 2026

    @MakenRosa

    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.

  4. Necmttn commented on Jun 21, 2026

    @Necmttn

    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.

  5. Eaco1412 commented on Sep 23, 2026

    @Eaco1412

    I can still reproduce this on Codex CLI 0.156.1 on Linux.

    A session disappeared from the normal codex resume picker, but the thread itself was still present in ~/.codex/thread_history_1.sqlite.

    I recovered its thread ID from thread_items, and direct resume worked successfully:

    codex resume <thread-id>

    So in this case the conversation data was not lost; the thread was still resumable but was missing from normal session discovery.

    The affected workspace was a Git worktree.

    Happy to provide additional sanitized local DB/index diagnostics if useful.

  6. dnikolayev commented on Oct 5, 2026

    @dnikolayev

    Possibly it might happen if you opened Codex in a different working directory.
    Frankly, I reproduced the issue in the same directory in two different terminal tabs.
    As a quick solution to select the proper session(in my case): codex resume --all

    Just to confirm the bug in 0.160 (mac os) - same issue (codex resume or /resume within the opened Codex CLI):
    Image

  7. dnikolayev commented on Oct 5, 2026

    @dnikolayev

    I dug into this a bit more. First, a correction to my earlier comment: the terminal tabs I checked were running from different directories, which explained the empty list. So --all showing sessions isn’t enough to confirm this bug on its own.
    I could still reproduce the issue with synthetic session files:
    If the first user message appears beyond the preview scan limit, the filesystem listing skips the session even though SQLite has valid metadata for it. The filtered listing then repairs the database entry but returns the filesystem results, so the session stays missing.

    I’ve put a proposed fix in dnikolayev/codex#1, with regression tests for that case and the pagination edge cases. Recovery runs only when the database and filesystem results disagree. Normal listings don’t gain extra queries or file reads, and warm benchmarks stayed within a few percent of the original code.

    All 154 rollout tests and 27 state-thread tests pass, as do formatting and focused Clippy checks. Full fork CI still has failures, including unavailable upstream runners and checks failing on unchanged code, so I can’t call the entire CI run green yet.

    @zm-oai @jif-oai

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

    CLIIssues related to the Codex CLITUIIssues related to the terminal user interface: text input, menus and dialogs, and terminal displayapp-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