Fixes#54618.
## Summary
After upgrading from `v0.232.x` to `v0.233.5+`, many users reported that
most
of their agent threads "disappeared" from the sidebar. The threads are
still
on disk in the legacy `threads.db` — but they never make it into
`sidebar_threads`, which is the only table the new sidebar UI reads
from, so
they're unreachable from the UI.
The root cause is a race between `ThreadStore::reload` and
`migrate_thread_metadata`:
- `ThreadStore::new` constructs with an empty in-memory `Vec` and kicks
off
`reload()` as a fire-and-forget task.
- `migrate_thread_metadata` runs during `agent_ui::init` and reads
`ThreadStore::global(cx).read(cx).entries()` synchronously, without
awaiting that reload.
- On cold boot the migration observes an empty iterator, early-returns
on
`to_migrate.is_empty()`, and never populates `sidebar_threads`. The
migration runs every launch, so it keeps losing the race forever.
Empirically, the only rows that *did* end up in `sidebar_threads` for
affected users came from `handle_conversation_event` writing rows when
the
user actively interacted with a thread. That's why users would typically
see
a handful of recently-touched threads rather than their full history.
## Fix
1. `ThreadStore` now tracks its current reload as a `Shared<Task<()>>`
and
exposes it via `reload_task()`. `migrate_thread_metadata` awaits this
before reading `entries()`.
2. Move the top-5-per-project unarchive pass out from under the
`is_first_migration` guard. Previously the rescue only ran when
`sidebar_threads` was empty, which meant any user who had even one
pre-existing row (e.g. from interacting with a thread on a prior launch)
got every subsequent batch of migrated threads archived with no rescue.
Running the rescue per-batch is stateless and idempotent — a user who
bounces between older releases and newer ones still gets their top 5 per
project surfaced each time a new batch is migrated.
No new KVP flag or one-shot backfill is needed: because the migration
already dedupes by session_id and runs on every launch, the next cold
boot
on a fixed build picks up any legacy threads that earlier launches
missed.
## Structure
The PR is split into two commits to make the diff easy to verify:
1. **Regression test** — fails on `main`, reproduces the cold-boot race
by
seeding the legacy DB, reinitializing `ThreadStore` to get a fresh empty
cache, and running the migration without parking first.
2. **The fix** — gates entries reads on `reload_task().await`, and
adjusts
the per-batch rescue policy. Updates one existing test's assertions to
match the new per-batch rescue policy.
## Validation
- The regression test fails on `main`, passes after the fix.
- All `agent_ui`, `agent::thread_store`, and `sidebar` lib tests pass.
- `./script/clippy -p agent -p agent_ui` is clean.
- End-to-end repro with a real on-disk data dir: launched `v0.230.2 →
v0.231.2 → v0.232.3 → v0.233.8` as a non-staff user, creating 15 threads
and interacting with one, reproduced the reported state (1 thread in
sidebar, 14 missing). Launched a debug build of this branch against the
same data dir: `Migrating 14 thread store entries` in the log, 15 rows
in
`sidebar_threads` with 6 unarchived (the interacted-with thread plus the
5 most recent), 9 archived and reachable from the archive view.
Release Notes:
- Fixed agent threads appearing to be missing after upgrading to the
parallel-agents sidebar. The thread-metadata migration was racing
against the on-disk thread store's async reload and skipping itself on
every launch. Upgrading to this build runs the migration successfully;
previously-missing threads are surfaced either in the sidebar (5 most
recent per project) or in the archive view.