@@ -90,21 +90,10 @@ export class SSHRuntime implements Runtime {
9090 parts . push ( command ) ;
9191
9292 // Join all parts with && to ensure each step succeeds before continuing
93- let fullCommand = parts . join ( " && " ) ;
94-
95- // Wrap command with timeout if specified
96- // This ensures the remote command is killed even if the SSH connection persists
97- // (e.g., due to ControlMaster multiplexing)
98- if ( options . timeout !== undefined ) {
99- // Use timeout command with KILL signal after timeout expires
100- // The timeout value is slightly longer than our local timeout to ensure
101- // the local timeout fires first in normal cases
102- const remoteTimeout = Math . ceil ( options . timeout ) + 1 ;
103- fullCommand = `timeout --signal=KILL ${ remoteTimeout } bash -c ${ shescape . quote ( fullCommand ) } ` ;
104- }
93+ const fullCommand = parts . join ( " && " ) ;
10594
10695 // Wrap in bash -c with shescape for safe shell execution
107- const remoteCommand = options . timeout ? fullCommand : `bash -c ${ shescape . quote ( fullCommand ) } ` ;
96+ const remoteCommand = `bash -c ${ shescape . quote ( fullCommand ) } ` ;
10897
10998 // Build SSH args
11099 const sshArgs : string [ ] = [ "-T" ] ;
@@ -128,9 +117,19 @@ export class SSHRuntime implements Runtime {
128117 // ControlMaster=auto: Create master connection if none exists, otherwise reuse
129118 // ControlPath: Unix socket path for multiplexing
130119 // ControlPersist=60: Keep master connection alive for 60s after last session
131- sshArgs . push ( "-o" , "ControlMaster=auto" ) ;
132- sshArgs . push ( "-o" , `ControlPath=${ this . controlPath } ` ) ;
133- sshArgs . push ( "-o" , "ControlPersist=60" ) ;
120+ //
121+ // IMPORTANT: Disable multiplexing when timeout is set, because:
122+ // - Killing an SSH client with multiplexing doesn't kill the remote command
123+ // - The remote command continues running under the master connection
124+ // - Without multiplexing, killing SSH reliably terminates the remote command
125+ if ( options . timeout === undefined ) {
126+ sshArgs . push ( "-o" , "ControlMaster=auto" ) ;
127+ sshArgs . push ( "-o" , `ControlPath=${ this . controlPath } ` ) ;
128+ sshArgs . push ( "-o" , "ControlPersist=60" ) ;
129+ } else {
130+ // Explicitly disable multiplexing for timed commands
131+ sshArgs . push ( "-o" , "ControlMaster=no" ) ;
132+ }
134133
135134 // Set comprehensive timeout options to ensure SSH respects the timeout
136135 // ConnectTimeout: Maximum time to wait for connection establishment (DNS, TCP handshake, SSH auth)
0 commit comments