Fix SSH reconnection message (#48190)

localcc created

Closes #34531

Release Notes:

- N/A

Change summary

crates/remote/src/transport/ssh.rs | 58 ++++++++++++++++++++-----------
1 file changed, 38 insertions(+), 20 deletions(-)

Detailed changes

crates/remote/src/transport/ssh.rs 🔗

@@ -315,7 +315,8 @@ impl RemoteConnection for SshRemoteConnection {
                 *ssh_path_style,
                 ssh_shell,
                 *ssh_shell_kind,
-                socket.ssh_args(),
+                socket.ssh_command_options(),
+                &socket.connection_options.ssh_destination(),
                 interactive,
             )
         } else {
@@ -329,7 +330,8 @@ impl RemoteConnection for SshRemoteConnection {
                 *ssh_path_style,
                 ssh_shell,
                 *ssh_shell_kind,
-                socket.ssh_args(),
+                socket.ssh_command_options(),
+                &socket.connection_options.ssh_destination(),
                 interactive,
             )
         }
@@ -340,12 +342,13 @@ impl RemoteConnection for SshRemoteConnection {
         forwards: Vec<(u16, String, u16)>,
     ) -> Result<CommandTemplate> {
         let Self { socket, .. } = self;
-        let mut args = socket.ssh_args();
+        let mut args = socket.ssh_command_options();
         args.push("-N".into());
         for (local_port, host, remote_port) in forwards {
             args.push("-L".into());
             args.push(format!("{local_port}:{host}:{remote_port}"));
         }
+        args.push(socket.connection_options.ssh_destination());
         Ok(CommandTemplate {
             program: "ssh".into(),
             args,
@@ -1208,20 +1211,23 @@ impl SshSocket {
         cmd
     }
 
-    // On Windows, we need to use `SSH_ASKPASS` to provide the password to ssh.
-    // On Linux, we use the `ControlPath` option to create a socket file that ssh can use to
-    fn ssh_args(&self) -> Vec<String> {
-        let mut arguments = self.connection_options.additional_args();
+    // Returns the SSH command-line options (without the destination) for building commands.
+    // On Linux, this includes the ControlPath option to reuse the existing connection.
+    // Note: The destination must be added separately after all options to ensure proper
+    // SSH command structure: ssh [options] destination [command]
+    fn ssh_command_options(&self) -> Vec<String> {
+        let arguments = self.connection_options.additional_args();
         #[cfg(not(windows))]
-        arguments.extend(vec![
-            "-o".to_string(),
-            "ControlMaster=no".to_string(),
-            "-o".to_string(),
-            format!("ControlPath={}", self.socket_path.display()),
-            self.connection_options.ssh_destination(),
-        ]);
-        #[cfg(windows)]
-        arguments.push(self.connection_options.ssh_destination());
+        let arguments = {
+            let mut args = arguments;
+            args.extend(vec![
+                "-o".to_string(),
+                "ControlMaster=no".to_string(),
+                "-o".to_string(),
+                format!("ControlPath={}", self.socket_path.display()),
+            ]);
+            args
+        };
         arguments
     }
 
@@ -1571,7 +1577,8 @@ fn build_command_posix(
     ssh_path_style: PathStyle,
     ssh_shell: &str,
     ssh_shell_kind: ShellKind,
-    ssh_args: Vec<String>,
+    ssh_options: Vec<String>,
+    ssh_destination: &str,
     interactive: Interactive,
 ) -> Result<CommandTemplate> {
     use std::fmt::Write as _;
@@ -1632,7 +1639,7 @@ fn build_command_posix(
     };
 
     let mut args = Vec::new();
-    args.extend(ssh_args);
+    args.extend(ssh_options);
 
     if let Some((local_port, host, remote_port)) = port_forward {
         args.push("-L".into());
@@ -1648,6 +1655,8 @@ fn build_command_posix(
         // -T disables pseudo-TTY allocation (for non-interactive piped stdio)
         Interactive::No => args.push("-T".into()),
     }
+    // The destination must come after all options but before the command
+    args.push(ssh_destination.into());
     args.push(exec);
 
     Ok(CommandTemplate {
@@ -1667,7 +1676,8 @@ fn build_command_windows(
     ssh_path_style: PathStyle,
     ssh_shell: &str,
     _ssh_shell_kind: ShellKind,
-    ssh_args: Vec<String>,
+    ssh_options: Vec<String>,
+    ssh_destination: &str,
     interactive: Interactive,
 ) -> Result<CommandTemplate> {
     use base64::Engine as _;
@@ -1719,7 +1729,7 @@ fn build_command_windows(
     };
 
     let mut args = Vec::new();
-    args.extend(ssh_args);
+    args.extend(ssh_options);
 
     if let Some((local_port, host, remote_port)) = port_forward {
         args.push("-L".into());
@@ -1736,6 +1746,9 @@ fn build_command_windows(
         Interactive::No => args.push("-T".into()),
     }
 
+    // The destination must come after all options but before the command
+    args.push(ssh_destination.into());
+
     // Windows OpenSSH server incorrectly escapes the command string when the PTY is used.
     // The simplest way to work around this is to use a base64 encoded command, which doesn't require escaping.
     let utf16_bytes: Vec<u16> = exec.encode_utf16().collect();
@@ -1774,6 +1787,7 @@ mod tests {
             "/bin/bash",
             ShellKind::Posix,
             vec!["-o".to_string(), "ControlMaster=auto".to_string()],
+            "user@host",
             Interactive::No,
         )?;
         assert_eq!(command.program, "ssh");
@@ -1793,6 +1807,7 @@ mod tests {
             "/bin/fish",
             ShellKind::Fish,
             vec!["-p".to_string(), "2222".to_string()],
+            "user@host",
             Interactive::Yes,
         )?;
 
@@ -1804,6 +1819,7 @@ mod tests {
                 "2222",
                 "-q",
                 "-t",
+                "user@host",
                 "cd \"$HOME/work\" && exec env INPUT_VA=val remote_program arg1 arg2"
             ]
         );
@@ -1825,6 +1841,7 @@ mod tests {
             "/bin/fish",
             ShellKind::Fish,
             vec!["-p".to_string(), "2222".to_string()],
+            "user@host",
             Interactive::Yes,
         )?;
 
@@ -1838,6 +1855,7 @@ mod tests {
                 "1:foo:2",
                 "-q",
                 "-t",
+                "user@host",
                 "cd && exec env INPUT_VA=val /bin/fish -l"
             ]
         );