Skip to content

Commit 50c7986

Browse files
committed
🤖 refactor: simplify SSHRuntime tilde resolution to single cached method
1 parent 8e3de5d commit 50c7986

File tree

1 file changed

+27
-2
lines changed

1 file changed

+27
-2
lines changed

src/node/runtime/SSHRuntime.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export interface SSHRuntimeConfig {
8080
export class SSHRuntime implements Runtime {
8181
private readonly config: SSHRuntimeConfig;
8282
private readonly controlPath: string;
83+
/** Cached resolved bgOutputDir (tilde expanded to absolute path) */
84+
private resolvedBgOutputDir: string | null = null;
8385

8486
constructor(config: SSHRuntimeConfig) {
8587
// Note: srcBaseDir may contain tildes - they will be resolved via resolvePath() before use
@@ -91,6 +93,27 @@ export class SSHRuntime implements Runtime {
9193
this.controlPath = getControlPath(config);
9294
}
9395

96+
/**
97+
* Get resolved background output directory (tilde expanded), caching the result.
98+
* This ensures all background process paths are absolute from the start.
99+
*/
100+
private async getBgOutputDir(): Promise<string> {
101+
if (this.resolvedBgOutputDir !== null) {
102+
return this.resolvedBgOutputDir;
103+
}
104+
105+
let dir = this.config.bgOutputDir ?? "/tmp/mux-bashes";
106+
107+
if (dir === "~" || dir.startsWith("~/")) {
108+
const result = await execBuffered(this, 'echo "$HOME"', { cwd: "/", timeout: 10 });
109+
const home = result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : "/tmp";
110+
dir = dir === "~" ? home : `${home}/${dir.slice(2)}`;
111+
}
112+
113+
this.resolvedBgOutputDir = dir;
114+
return this.resolvedBgOutputDir;
115+
}
116+
94117
/**
95118
* Get SSH configuration (for PTY terminal spawning)
96119
*/
@@ -294,7 +317,7 @@ export class SSHRuntime implements Runtime {
294317
// Generate unique process ID and compute output directory
295318
// /tmp is cleaned by OS, so no explicit cleanup needed
296319
const processId = `bg-${randomBytes(4).toString("hex")}`;
297-
const bgOutputDir = this.config.bgOutputDir ?? "/tmp/mux-bashes";
320+
const bgOutputDir = await this.getBgOutputDir();
298321
const outputDir = `${bgOutputDir}/${options.workspaceId}/${processId}`;
299322
const stdoutPath = `${outputDir}/stdout.log`;
300323
const stderrPath = `${outputDir}/stderr.log`;
@@ -354,7 +377,8 @@ export class SSHRuntime implements Runtime {
354377

355378
try {
356379
// No timeout - the spawn command backgrounds the process and returns immediately,
357-
// but if wrapped in `timeout`, it would wait for the backgrounded process to exit
380+
// but if wrapped in `timeout`, it would wait for the backgrounded process to exit.
381+
// SSH connection hangs are protected by ConnectTimeout (see buildSshArgs in this file).
358382
const result = await execBuffered(this, spawnCommand, {
359383
cwd: "/", // cwd doesn't matter, we cd in the wrapper
360384
});
@@ -378,6 +402,7 @@ export class SSHRuntime implements Runtime {
378402

379403
log.debug(`SSHRuntime.spawnBackground: Spawned with PID ${pid}`);
380404

405+
// outputDir is already absolute (getBgOutputDir resolves tildes upfront)
381406
const handle = new SSHBackgroundHandle(this, pid, outputDir);
382407
return { success: true, handle, pid };
383408
} catch (error) {

0 commit comments

Comments
 (0)