Fix 'no thread found' error when restoring agent thread on workspace open (#49045)

Richard Feldman created

When the multi-workspace thread restoration feature serializes the
active thread's session ID, it includes empty threads that were never
persisted to the database (because `NativeAgent::save_thread` skips
empty threads). On restart, `AgentPanel::load()` tries to restore this
thread by calling `load_agent_thread()`, which queries the SQLite DB.
Since the empty thread was never saved, the query returns `None`, which
becomes the error: "no thread found with ID: SessionId(...)". This shows
as "Error Loading Zed Agent" / "Failed to Launch" in the UI.

**Fix:**
Before attempting to restore a serialized thread on panel load, query
the DB to verify the thread actually exists. If it doesn't, log an error
and skip restoration (the panel falls back to creating a new thread as
normal).

Closes AI-19

Release Notes:

- Fixed an "Error Loading Zed Agent" error that could appear when
opening a workspace where the previous agent thread was empty.

Change summary

crates/agent_ui/src/agent_panel.rs | 54 ++++++++++++++++++++-----------
1 file changed, 35 insertions(+), 19 deletions(-)

Detailed changes

crates/agent_ui/src/agent_panel.rs 🔗

@@ -578,23 +578,41 @@ impl AgentPanel {
                     });
                 }
 
-                if let Some(thread_info) = serialized_panel.and_then(|p| p.last_active_thread) {
-                    let agent_type = thread_info.agent_type.clone();
-                    let session_info = AgentSessionInfo {
-                        session_id: acp::SessionId::new(thread_info.session_id),
-                        cwd: thread_info.cwd,
-                        title: thread_info.title.map(SharedString::from),
-                        updated_at: None,
-                        meta: None,
-                    };
-                    panel.update(cx, |panel, cx| {
-                        panel.selected_agent = agent_type;
-                        panel.load_agent_thread(session_info, window, cx);
-                    });
-                }
                 panel
             })?;
 
+            if let Some(thread_info) = serialized_panel.and_then(|p| p.last_active_thread) {
+                let session_id = acp::SessionId::new(thread_info.session_id.clone());
+                let load_task = panel.update(cx, |panel, cx| {
+                    let thread_store = panel.thread_store.clone();
+                    thread_store.update(cx, |store, cx| store.load_thread(session_id, cx))
+                });
+                let thread_exists = load_task
+                    .await
+                    .map(|thread: Option<agent::DbThread>| thread.is_some())
+                    .unwrap_or(false);
+
+                if thread_exists {
+                    panel.update_in(cx, |panel, window, cx| {
+                        panel.selected_agent = thread_info.agent_type.clone();
+                        let session_info = AgentSessionInfo {
+                            session_id: acp::SessionId::new(thread_info.session_id),
+                            cwd: thread_info.cwd,
+                            title: thread_info.title.map(SharedString::from),
+                            updated_at: None,
+                            meta: None,
+                        };
+                        panel.load_agent_thread(session_info, window, cx);
+                    })?;
+                } else {
+                    log::error!(
+                        "could not restore last active thread: \
+                         no thread found in database with ID {:?}",
+                        thread_info.session_id
+                    );
+                }
+            }
+
             Ok(panel)
         })
     }
@@ -3497,7 +3515,9 @@ mod tests {
             .expect("panel B load should succeed");
         cx.run_until_parked();
 
-        // Workspace A should restore its thread, width, and agent type
+        // Workspace A should restore width and agent type, but the thread
+        // should NOT be restored because the stub agent never persisted it
+        // to the database (the load-side validation skips missing threads).
         loaded_a.read_with(cx, |panel, _cx| {
             assert_eq!(
                 panel.width,
@@ -3508,10 +3528,6 @@ mod tests {
                 panel.selected_agent, agent_type_a,
                 "workspace A agent type should be restored"
             );
-            assert!(
-                panel.active_thread_view().is_some(),
-                "workspace A should have its active thread restored"
-            );
         });
 
         // Workspace B should restore its own width and agent type, with no thread