Dismiss stale remote connection modal when switching back to local workspace (#53575)

When the sidebar opens a remote SSH project, it shows a
`RemoteConnectionModal` on the currently active (local) workspace. After
the connection succeeds and a new remote workspace is created and
activated, the modal on the local workspace was never dismissed.
Switching back to the local workspace (e.g. by activating a thread)
would re-render the local workspace's modal layer, revealing the stale
"Starting proxy..." modal.

Other code paths that show this modal (`recent_projects`, `git_ui`)
already call `modal.finished(cx)` after the connection completes. The
sidebar and agent panel paths were missing this cleanup.

## Changes

- **`remote_connection`**: Added `dismiss_connection_modal()`, a public
utility that finds and dismisses any active `RemoteConnectionModal` on a
given workspace.
- **`sidebar`**: Fixed two call sites (`open_workspace_for_group` and
`open_workspace_and_activate_thread`) to dismiss the modal after the
connection task completes, regardless of success or failure.
- **`agent_ui`**: Fixed `open_worktree_workspace_and_start_thread` to
dismiss the modal after workspace creation completes.

Release Notes:

- (Preview only) Fixed a spurious "Starting proxy..." modal appearing
and hanging when switching back to a local project after opening a
remote SSH project in a multi-project workspace.
This commit is contained in:
Eric Holk authored and GitHub committed 2026-04-10 02:22:55 +00:00
1 parent 5d9ee5fe78
commit d634055a19
3 files changed
+65 -32

No files matched your search

+25 -20
View File
@@ -3190,28 +3190,33 @@ impl AgentPanel {
let window_handle = window_handle
.ok_or_else(|| anyhow!("No window handle available for workspace creation"))?;
let workspace_task = window_handle.update(cx, |multi_workspace, window, cx| {
let path_list = PathList::new(&all_paths);
let active_workspace = multi_workspace.workspace().clone();
let (workspace_task, modal_workspace) =
window_handle.update(cx, |multi_workspace, window, cx| {
let path_list = PathList::new(&all_paths);
let active_workspace = multi_workspace.workspace().clone();
let modal_workspace = active_workspace.clone();
multi_workspace.find_or_create_workspace(
path_list,
remote_connection_options,
None,
move |connection_options, window, cx| {
remote_connection::connect_with_modal(
&active_workspace,
connection_options,
window,
cx,
)
},
window,
cx,
)
})?;
let task = multi_workspace.find_or_create_workspace(
path_list,
remote_connection_options,
None,
move |connection_options, window, cx| {
remote_connection::connect_with_modal(
&active_workspace,
connection_options,
window,
cx,
)
},
window,
cx,
);
(task, modal_workspace)
})?;
let new_workspace = workspace_task.await?;
let result = workspace_task.await;
remote_connection::dismiss_connection_modal(&modal_workspace, cx);
let new_workspace = result?;
let panels_task = new_workspace.update(cx, |workspace, _cx| workspace.take_panels_task());
@@ -574,6 +574,23 @@ pub fn connect_with_modal(
})
}
/// Dismisses any active [`RemoteConnectionModal`] on the given workspace.
///
/// This should be called after a remote connection attempt completes
/// (success or failure) when the modal was shown on a workspace that may
/// outlive the connection flow — for example, when the modal is shown
/// on a local workspace before switching to a newly-created remote
/// workspace.
pub fn dismiss_connection_modal(workspace: &Entity<Workspace>, cx: &mut gpui::AsyncWindowContext) {
workspace
.update_in(cx, |workspace, _window, cx| {
if let Some(modal) = workspace.active_modal::<RemoteConnectionModal>(cx) {
modal.update(cx, |modal, cx| modal.finished(cx));
}
})
.ok();
}
/// Creates a [`RemoteClient`] by reusing an existing connection from the
/// global pool. No interactive UI is shown. This should only be called
/// when [`remote::has_active_connection`] returns `true`.
+23 -12
View File
@@ -753,19 +753,26 @@ impl Sidebar {
let host = project_group_key.host();
let provisional_key = Some(project_group_key.clone());
let active_workspace = multi_workspace.read(cx).workspace().clone();
let modal_workspace = active_workspace.clone();
multi_workspace
.update(cx, |this, cx| {
this.find_or_create_workspace(
path_list,
host,
provisional_key,
|options, window, cx| connect_remote(active_workspace, options, window, cx),
window,
cx,
)
})
.detach_and_log_err(cx);
let task = multi_workspace.update(cx, |this, cx| {
this.find_or_create_workspace(
path_list,
host,
provisional_key,
|options, window, cx| connect_remote(active_workspace, options, window, cx),
window,
cx,
)
});
cx.spawn_in(window, async move |_this, cx| {
let result = task.await;
remote_connection::dismiss_connection_modal(&modal_workspace, cx);
result?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
/// Rebuilds the sidebar contents from current workspace and thread state.
@@ -2292,6 +2299,7 @@ impl Sidebar {
let host = project_group_key.host();
let provisional_key = Some(project_group_key.clone());
let active_workspace = multi_workspace.read(cx).workspace().clone();
let modal_workspace = active_workspace.clone();
let open_task = multi_workspace.update(cx, |this, cx| {
this.find_or_create_workspace(
@@ -2306,6 +2314,9 @@ impl Sidebar {
cx.spawn_in(window, async move |this, cx| {
let result = open_task.await;
// Dismiss the modal as soon as the open attempt completes so
// failures or cancellations do not leave a stale connection modal behind.
remote_connection::dismiss_connection_modal(&modal_workspace, cx);
if result.is_err() || is_remote {
this.update(cx, |this, _cx| {