From 3e14b1e0035e65f78c6e901c377b40a244b04d27 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:57:16 +0000 Subject: [PATCH] remote: Recognize WSL interop to open browser for codex web login (#44136) (cherry-pick to preview) (#44159) Cherry-pick of #44136 to preview ---- Closes #41521 Release Notes: - Fixed codex web login not working on wsl remotes if no browser is installed Co-authored-by: Ben Brandt Co-authored-by: Lukas Wirth Co-authored-by: Ben Brandt --- crates/client/src/client.rs | 4 ++++ crates/project/src/agent_server_store.rs | 10 ++++---- crates/remote/Cargo.toml | 1 - crates/remote/src/remote_client.rs | 30 ++++++++++++++++++++---- crates/remote/src/transport.rs | 6 +---- crates/remote/src/transport/ssh.rs | 4 ++++ crates/remote/src/transport/wsl.rs | 24 +++++++++++++++++++ crates/remote_server/src/unix.rs | 12 ++++++++-- crates/rpc/src/proto_client.rs | 5 ++++ 9 files changed, 79 insertions(+), 17 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 96b15dc9fb13deea3cdc706f1927c4d6f016b57a..6d6d229b940433ceac4c80f11891319550d269a2 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1723,6 +1723,10 @@ impl ProtoClient for Client { fn is_via_collab(&self) -> bool { true } + + fn has_wsl_interop(&self) -> bool { + false + } } /// prefix for the zed:// url scheme diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index ef12e222009a59430a3396cae7971ac7593e82c3..95afdd09c15b9970d7eb637e6df99502d3bc3b67 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -453,7 +453,9 @@ impl AgentServerStore { .clone() .and_then(|settings| settings.custom_command()), http_client: http_client.clone(), - is_remote: downstream_client.is_some(), + no_browser: downstream_client + .as_ref() + .is_some_and(|(_, client)| !client.has_wsl_interop()), }), ); self.external_agents.insert( @@ -1355,7 +1357,7 @@ struct LocalCodex { project_environment: Entity, http_client: Arc, custom_command: Option, - is_remote: bool, + no_browser: bool, } impl ExternalAgentServer for LocalCodex { @@ -1375,7 +1377,7 @@ impl ExternalAgentServer for LocalCodex { .map(|root_dir| Path::new(root_dir)) .unwrap_or(paths::home_dir()) .into(); - let is_remote = self.is_remote; + let no_browser = self.no_browser; cx.spawn(async move |cx| { let mut env = project_environment @@ -1388,7 +1390,7 @@ impl ExternalAgentServer for LocalCodex { })? .await .unwrap_or_default(); - if is_remote { + if no_browser { env.insert("NO_BROWSER".to_owned(), "1".to_owned()); } diff --git a/crates/remote/Cargo.toml b/crates/remote/Cargo.toml index 07eb7d795e21c2f4b99817e301f6d8687c4aab60..ae32cd5cb10c2bf4c65b3b8ae51bf20e7e3ad15a 100644 --- a/crates/remote/Cargo.toml +++ b/crates/remote/Cargo.toml @@ -43,7 +43,6 @@ urlencoding.workspace = true util.workspace = true which.workspace = true - [dev-dependencies] gpui = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] } diff --git a/crates/remote/src/remote_client.rs b/crates/remote/src/remote_client.rs index 85b19ba25ca7187dfb400eb4716234bb3716ba9c..b0f9914c90545263a830ec034512a7e423109409 100644 --- a/crates/remote/src/remote_client.rs +++ b/crates/remote/src/remote_client.rs @@ -328,8 +328,15 @@ impl RemoteClient { let (incoming_tx, incoming_rx) = mpsc::unbounded::(); let (connection_activity_tx, connection_activity_rx) = mpsc::channel::<()>(1); - let client = - cx.update(|cx| ChannelClient::new(incoming_rx, outgoing_tx, cx, "client"))?; + let client = cx.update(|cx| { + ChannelClient::new( + incoming_rx, + outgoing_tx, + cx, + "client", + remote_connection.has_wsl_interop(), + ) + })?; let path_style = remote_connection.path_style(); let this = cx.new(|_| Self { @@ -420,8 +427,9 @@ impl RemoteClient { outgoing_tx: mpsc::UnboundedSender, cx: &App, name: &'static str, + has_wsl_interop: bool, ) -> AnyProtoClient { - ChannelClient::new(incoming_rx, outgoing_tx, cx, name).into() + ChannelClient::new(incoming_rx, outgoing_tx, cx, name, has_wsl_interop).into() } pub fn shutdown_processes( @@ -921,8 +929,8 @@ impl RemoteClient { }); let (outgoing_tx, _) = mpsc::unbounded::(); let (_, incoming_rx) = mpsc::unbounded::(); - let server_client = - server_cx.update(|cx| ChannelClient::new(incoming_rx, outgoing_tx, cx, "fake-server")); + let server_client = server_cx + .update(|cx| ChannelClient::new(incoming_rx, outgoing_tx, cx, "fake-server", false)); let connection: Arc = Arc::new(fake::FakeRemoteConnection { connection_options: opts.clone(), server_cx: fake::SendableCx::new(server_cx), @@ -1140,6 +1148,7 @@ pub trait RemoteConnection: Send + Sync { fn path_style(&self) -> PathStyle; fn shell(&self) -> String; fn default_system_shell(&self) -> String; + fn has_wsl_interop(&self) -> bool; #[cfg(any(test, feature = "test-support"))] fn simulate_disconnect(&self, _: &AsyncApp) {} @@ -1188,6 +1197,7 @@ struct ChannelClient { name: &'static str, task: Mutex>>, remote_started: Signal<()>, + has_wsl_interop: bool, } impl ChannelClient { @@ -1196,6 +1206,7 @@ impl ChannelClient { outgoing_tx: mpsc::UnboundedSender, cx: &App, name: &'static str, + has_wsl_interop: bool, ) -> Arc { Arc::new_cyclic(|this| Self { outgoing_tx: Mutex::new(outgoing_tx), @@ -1211,6 +1222,7 @@ impl ChannelClient { &cx.to_async(), )), remote_started: Signal::new(cx), + has_wsl_interop, }) } @@ -1489,6 +1501,10 @@ impl ProtoClient for ChannelClient { fn is_via_collab(&self) -> bool { false } + + fn has_wsl_interop(&self) -> bool { + self.has_wsl_interop + } } #[cfg(any(test, feature = "test-support"))] @@ -1652,6 +1668,10 @@ mod fake { fn default_system_shell(&self) -> String { "sh".to_owned() } + + fn has_wsl_interop(&self) -> bool { + false + } } pub(super) struct Delegate; diff --git a/crates/remote/src/transport.rs b/crates/remote/src/transport.rs index 211851c0629c13f1f79ce425cafc582899d1b58f..7441ede609dbfe0e4c74c3f3738bd07d209a37ec 100644 --- a/crates/remote/src/transport.rs +++ b/crates/remote/src/transport.rs @@ -131,11 +131,7 @@ async fn build_remote_server_from_source( let build_remote_server = std::env::var("ZED_BUILD_REMOTE_SERVER").unwrap_or("nocompress".into()); - if build_remote_server == "false" - || build_remote_server == "no" - || build_remote_server == "off" - || build_remote_server == "0" - { + if let "false" | "no" | "off" | "0" = &*build_remote_server { return Ok(None); } diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index bf537a3d6715eb8492fa87b802a26a111ec402b7..20cd0c5ff4b427d3a37882603ce2962db9e4e1e0 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -394,6 +394,10 @@ impl RemoteConnection for SshRemoteConnection { fn path_style(&self) -> PathStyle { self.ssh_path_style } + + fn has_wsl_interop(&self) -> bool { + false + } } impl SshRemoteConnection { diff --git a/crates/remote/src/transport/wsl.rs b/crates/remote/src/transport/wsl.rs index c6fa154ba09928efc04bb3ac15ad98b1db0671c0..670f122012ea1ab39b5905995f70c01d1dcf439c 100644 --- a/crates/remote/src/transport/wsl.rs +++ b/crates/remote/src/transport/wsl.rs @@ -47,6 +47,7 @@ pub(crate) struct WslRemoteConnection { shell: String, shell_kind: ShellKind, default_system_shell: String, + has_wsl_interop: bool, connection_options: WslConnectionOptions, } @@ -71,6 +72,7 @@ impl WslRemoteConnection { shell: String::new(), shell_kind: ShellKind::Posix, default_system_shell: String::from("/bin/sh"), + has_wsl_interop: false, }; delegate.set_status(Some("Detecting WSL environment"), cx); this.shell = this @@ -79,6 +81,15 @@ impl WslRemoteConnection { .context("failed detecting shell")?; log::info!("Remote shell discovered: {}", this.shell); this.shell_kind = ShellKind::new(&this.shell, false); + this.has_wsl_interop = this.detect_has_wsl_interop().await.unwrap_or_default(); + log::info!( + "Remote has wsl interop {}", + if this.has_wsl_interop { + "enabled" + } else { + "disabled" + } + ); this.platform = this .detect_platform() .await @@ -115,6 +126,14 @@ impl WslRemoteConnection { .unwrap_or_else(|| "/bin/sh".to_string())) } + async fn detect_has_wsl_interop(&self) -> Result { + Ok(self + .run_wsl_command_with_output("cat", &["/proc/sys/fs/binfmt_misc/WSLInterop"]) + .await + .inspect_err(|err| log::error!("Failed to detect wsl interop: {err}"))? + .contains("enabled")) + } + async fn windows_path_to_wsl_path(&self, source: &Path) -> Result { windows_path_to_wsl_path_impl(&self.connection_options, source).await } @@ -317,6 +336,7 @@ impl RemoteConnection for WslRemoteConnection { proxy_args.push(format!("{}={}", env_var, value)); } } + proxy_args.push(remote_binary_path.display(PathStyle::Posix).into_owned()); proxy_args.push("proxy".to_owned()); proxy_args.push("--identifier".to_owned()); @@ -489,6 +509,10 @@ impl RemoteConnection for WslRemoteConnection { fn default_system_shell(&self) -> String { self.default_system_shell.clone() } + + fn has_wsl_interop(&self) -> bool { + self.has_wsl_interop + } } /// `wslpath` is a executable available in WSL, it's a linux binary. diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 0407539a4c131d92202e3177cc95137062b039ec..8adeaa594738aaddbcdd2dbe6454ead8485ca212 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -199,6 +199,7 @@ fn start_server( listeners: ServerListeners, log_rx: Receiver>, cx: &mut App, + is_wsl_interop: bool, ) -> AnyProtoClient { // This is the server idle timeout. If no connection comes in this timeout, the server will shut down. const IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10 * 60); @@ -318,7 +319,7 @@ fn start_server( }) .detach(); - RemoteClient::proto_client_from_channels(incoming_rx, outgoing_tx, cx, "server") + RemoteClient::proto_client_from_channels(incoming_rx, outgoing_tx, cx, "server", is_wsl_interop) } fn init_paths() -> anyhow::Result<()> { @@ -407,8 +408,15 @@ pub fn execute_run( HeadlessProject::init(cx); + let is_wsl_interop = if cfg!(target_os = "linux") { + // See: https://learn.microsoft.com/en-us/windows/wsl/filesystems#disable-interoperability + matches!(std::fs::read_to_string("/proc/sys/fs/binfmt_misc/WSLInterop"), Ok(s) if s.contains("enabled")) + } else { + false + }; + log::info!("gpui app started, initializing server"); - let session = start_server(listeners, log_rx, cx); + let session = start_server(listeners, log_rx, cx, is_wsl_interop); GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx); git_hosting_providers::init(cx); diff --git a/crates/rpc/src/proto_client.rs b/crates/rpc/src/proto_client.rs index d7e3ba1e461b28ac264afcc05a8ae941e6da0c32..3850ff5820e6d73289b5714d6b880ecb584bf8d9 100644 --- a/crates/rpc/src/proto_client.rs +++ b/crates/rpc/src/proto_client.rs @@ -59,6 +59,7 @@ pub trait ProtoClient: Send + Sync { fn message_handler_set(&self) -> &parking_lot::Mutex; fn is_via_collab(&self) -> bool; + fn has_wsl_interop(&self) -> bool; } #[derive(Default)] @@ -510,6 +511,10 @@ impl AnyProtoClient { }, ); } + + pub fn has_wsl_interop(&self) -> bool { + self.0.client.has_wsl_interop() + } } fn to_any_envelope(