From 4049a4c91408501bb3658f3b248b5707b7fe0c87 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 23 Mar 2026 11:46:32 -0400 Subject: [PATCH] Fix removed workspace resurrecting via serialization race (#52035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In `remove_workspace`, the removed `Entity` could still have a pending `serialize_workspace` throttle timer (200ms). When that timer fired, `serialize_workspace_internal` would write the old `session_id` back to the DB — undoing the removal. On next restart, the workspace would reappear. The race window opens whenever any state change (worktree change, breakpoint change, etc.) triggers `serialize_workspace` within 200ms before `remove_workspace` is called. **Fix**: Before the DB cleanup task, `update` the removed workspace entity to: 1. `session_id.take()` — so any in-flight serialization writes `session_id: None` 2. `_schedule_serialize_workspace.take()` — cancel the pending throttle timer 3. `_serialize_workspace_task.take()` — cancel any actively running serialization task This mirrors what `remove_from_session` already does (clearing `session_id`), but `remove_workspace` was missing it. Release Notes: - Fixed a bug where a removed workspace could reappear on next launch due to a serialization race. --- crates/workspace/src/multi_workspace.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index c3ec2e1c61e1b038f91a57dddac0b7a7b89b337e..6e4b99ebbfe952d1e3faf3266b80ba5cd18aff34 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -649,6 +649,16 @@ impl MultiWorkspace { self.active_workspace_index -= 1; } + // Clear session_id and cancel any in-flight serialization on the + // removed workspace. Without this, a pending throttle timer from + // `serialize_workspace` could fire and write the old session_id + // back to the DB, resurrecting the workspace on next launch. + removed_workspace.update(cx, |workspace, _cx| { + workspace.session_id.take(); + workspace._schedule_serialize_workspace.take(); + workspace._serialize_workspace_task.take(); + }); + if let Some(workspace_id) = removed_workspace.read(cx).database_id() { let db = crate::persistence::WorkspaceDb::global(cx); self.pending_removal_tasks.retain(|task| !task.is_ready());