Skip to content

Commit 688d8c4

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 6c3f3d0 commit 688d8c4

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
@@ -91,21 +91,10 @@ export class SSHRuntime implements Runtime {
9191
parts.push(command);
9292

9393
// Join all parts with && to ensure each step succeeds before continuing
94-
let fullCommand = parts.join(" && ");
95-
96-
// Wrap command with timeout if specified
97-
// This ensures the remote command is killed even if the SSH connection persists
98-
// (e.g., due to ControlMaster multiplexing)
99-
if (options.timeout !== undefined) {
100-
// Use timeout command with KILL signal after timeout expires
101-
// The timeout value is slightly longer than our local timeout to ensure
102-
// the local timeout fires first in normal cases
103-
const remoteTimeout = Math.ceil(options.timeout) + 1;
104-
fullCommand = `timeout --signal=KILL ${remoteTimeout} bash -c ${shescape.quote(fullCommand)}`;
105-
}
94+
const fullCommand = parts.join(" && ");
10695

10796
// Wrap in bash -c with shescape for safe shell execution
108-
const remoteCommand = options.timeout ? fullCommand : `bash -c ${shescape.quote(fullCommand)}`;
97+
const remoteCommand = `bash -c ${shescape.quote(fullCommand)}`;
10998

11099
// Build SSH args
111100
const sshArgs: string[] = ["-T"];
@@ -129,9 +118,19 @@ export class SSHRuntime implements Runtime {
129118
// ControlMaster=auto: Create master connection if none exists, otherwise reuse
130119
// ControlPath: Unix socket path for multiplexing
131120
// ControlPersist=60: Keep master connection alive for 60s after last session
132-
sshArgs.push("-o", "ControlMaster=auto");
133-
sshArgs.push("-o", `ControlPath=${this.controlPath}`);
134-
sshArgs.push("-o", "ControlPersist=60");
121+
//
122+
// IMPORTANT: Disable multiplexing when timeout is set, because:
123+
// - Killing an SSH client with multiplexing doesn't kill the remote command
124+
// - The remote command continues running under the master connection
125+
// - Without multiplexing, killing SSH reliably terminates the remote command
126+
if (options.timeout === undefined) {
127+
sshArgs.push("-o", "ControlMaster=auto");
128+
sshArgs.push("-o", `ControlPath=${this.controlPath}`);
129+
sshArgs.push("-o", "ControlPersist=60");
130+
} else {
131+
// Explicitly disable multiplexing for timed commands
132+
sshArgs.push("-o", "ControlMaster=no");
133+
}
135134

136135
// Set comprehensive timeout options to ensure SSH respects the timeout
137136
// ConnectTimeout: Maximum time to wait for connection establishment (DNS, TCP handshake, SSH auth)

0 commit comments

Comments
 (0)