Skip to content

Commit 1fff59c

Browse files
committed
🤖 Disable SSH ControlMaster for timed commands
When ControlMaster multiplexing is enabled, killing the SSH client doesn't kill the remote command - it continues running under the master connection. Disable multiplexing for commands with timeout to ensure killing the SSH process reliably terminates the remote command. This is simpler than wrapping remote commands with timeout utilities.
1 parent 8d57f91 commit 1fff59c

File tree

1 file changed

+15
-16
lines changed

1 file changed

+15
-16
lines changed

src/runtime/SSHRuntime.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)