From 21fdf703e8c86d03ad620094c978d74fc75f20ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= <13155277+tomhoule@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:48:54 +0100 Subject: [PATCH] Fix "add custom context server" modal hanging indefinitely (#50085) --- .../configure_context_server_modal.rs | 23 ++++++++--- crates/project/src/context_server_store.rs | 41 +++++++++++++++---- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index 78c032a565522a7eac145add3f65568d559ceb24..38805f2c26693f168c7273afddf5aceea44f83e3 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -877,9 +877,14 @@ fn wait_for_context_server( context_server_id: ContextServerId, cx: &mut App, ) -> Task>> { + use std::time::Duration; + + const WAIT_TIMEOUT: Duration = Duration::from_secs(120); + let (tx, rx) = futures::channel::oneshot::channel(); let tx = Arc::new(Mutex::new(Some(tx))); + let context_server_id_for_timeout = context_server_id.clone(); let subscription = cx.subscribe(context_server_store, move |_, event, _cx| { let project::context_server_store::ServerStatusChangedEvent { server_id, status } = event; @@ -909,12 +914,20 @@ fn wait_for_context_server( } }); - cx.spawn(async move |_cx| { - let result = rx - .await - .map_err(|_| Arc::from("Context server store was dropped"))?; + cx.spawn(async move |cx| { + let timeout = cx.background_executor().timer(WAIT_TIMEOUT); + let result = futures::future::select(rx, timeout).await; drop(subscription); - result + match result { + futures::future::Either::Left((Ok(inner), _)) => inner, + futures::future::Either::Left((Err(_), _)) => { + Err(Arc::from("Context server store was dropped")) + } + futures::future::Either::Right(_) => Err(Arc::from(format!( + "Timed out waiting for context server `{}` to start. Check the Zed log for details.", + context_server_id_for_timeout + ))), + } }) } diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 2f67077a2c49014c63e7ae4927f5613351b3a4f4..88dc64fcbe8795ae4826dcaa2813744f525b9258 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -8,7 +8,7 @@ use std::time::Duration; use anyhow::{Context as _, Result}; use collections::{HashMap, HashSet}; use context_server::{ContextServer, ContextServerCommand, ContextServerId}; -use futures::{FutureExt as _, future::join_all}; +use futures::{FutureExt as _, future::Either, future::join_all}; use gpui::{App, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity, actions}; use itertools::Itertools; use registry::ContextServerDescriptorRegistry; @@ -141,6 +141,8 @@ impl ContextServerConfiguration { worktree_store: Entity, cx: &AsyncApp, ) -> Option { + const EXTENSION_COMMAND_TIMEOUT: Duration = Duration::from_secs(30); + match settings { ContextServerSettings::Stdio { enabled: _, @@ -155,18 +157,27 @@ impl ContextServerConfiguration { let descriptor = cx.update(|cx| registry.read(cx).context_server_descriptor(&id.0))?; - match descriptor.command(worktree_store, cx).await { - Ok(command) => Some(ContextServerConfiguration::Extension { + let command_future = descriptor.command(worktree_store, cx); + let timeout_future = cx.background_executor().timer(EXTENSION_COMMAND_TIMEOUT); + + match futures::future::select(command_future, timeout_future).await { + Either::Left((Ok(command), _)) => Some(ContextServerConfiguration::Extension { command, settings, remote, }), - Err(e) => { + Either::Left((Err(e), _)) => { log::error!( "Failed to create context server configuration from settings: {e:#}" ); None } + Either::Right(_) => { + log::error!( + "Timed out resolving command for extension context server {id}" + ); + None + } } } ContextServerSettings::Http { @@ -960,11 +971,23 @@ impl ContextServerStore { })??; for (id, config) in servers_to_start { - let (server, config) = - Self::create_context_server(this.clone(), id, config, cx).await?; - this.update(cx, |this, cx| { - this.run_server(server, config, cx); - })?; + match Self::create_context_server(this.clone(), id.clone(), config, cx).await { + Ok((server, config)) => { + this.update(cx, |this, cx| { + this.run_server(server, config, cx); + })?; + } + Err(err) => { + log::error!("{id} context server failed to create: {err:#}"); + this.update(cx, |_this, cx| { + cx.emit(ServerStatusChangedEvent { + server_id: id, + status: ContextServerStatus::Error(err.to_string().into()), + }); + cx.notify(); + })?; + } + } } Ok(())