Skip to content

Fix agent threads missing from sidebar after upgrade (#54723) (cherry-pick to stable)#54752

Merged
zed-zippy[bot] merged 1 commit intov0.233.xfrom
cherry-pick-v0.233.x-9adb4ea6
Apr 24, 2026
Merged

Fix agent threads missing from sidebar after upgrade (#54723) (cherry-pick to stable)#54752
zed-zippy[bot] merged 1 commit intov0.233.xfrom
cherry-pick-v0.233.x-9adb4ea6

Conversation

@zed-zippy
Copy link
Copy Markdown
Contributor

@zed-zippy zed-zippy Bot commented Apr 24, 2026

Cherry-pick of #54723 to stable


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.
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.
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Apr 24, 2026
@zed-community-bot zed-community-bot Bot added the bot Pull requests authored by a bot label Apr 24, 2026
@zed-zippy zed-zippy Bot merged commit fd7975b into v0.233.x Apr 24, 2026
41 checks passed
@zed-zippy zed-zippy Bot deleted the cherry-pick-v0.233.x-9adb4ea6 branch April 24, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot Pull requests authored by a bot cla-signed The user has signed the Contributor License Agreement

1 participant