Skip to content

Commit da4e2fd

Browse files
committed
🤖 fix: use POSIX paths for scripts in SSH workspaces
Change-Id: I448a26c7e23d4c2074c19fa3cbd7b9a4e7adbbd0 Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent fc8cdbc commit da4e2fd

File tree

3 files changed

+47
-5
lines changed

3 files changed

+47
-5
lines changed

src/node/services/scriptRunner.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as path from "path";
22
import { type Runtime } from "@/node/runtime/Runtime";
3-
import { getScriptPath } from "@/utils/scripts/discovery";
3+
import { getScriptPath, getScriptsDir } from "@/utils/scripts/discovery";
44
import { createBashTool } from "@/node/services/tools/bash";
55
import { writeFileString, readFileString, execBuffered } from "@/node/utils/runtime/helpers";
66
import { Ok, Err, type Result } from "@/common/types/result";
@@ -42,7 +42,7 @@ export async function runWorkspaceScript(
4242
}
4343

4444
const scriptPath = getScriptPath(workspacePath, scriptName);
45-
const scriptsDir = path.join(workspacePath, ".cmux", "scripts");
45+
const scriptsDir = getScriptsDir(workspacePath);
4646
const normalizedScriptPath = path.normalize(scriptPath);
4747
const normalizedScriptsDir = path.normalize(scriptsDir);
4848

src/utils/scripts/discovery.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, test, expect } from "bun:test";
22
import type { Runtime } from "@/node/runtime/Runtime";
3-
import { listScripts } from "./discovery";
3+
import { listScripts, getScriptPath } from "./discovery";
4+
import * as path from "path";
45

56
// Mock runtime for testing
67
function createMockRuntime(responses: Map<string, { stdout: string; exitCode: number }>): Runtime {
@@ -215,3 +216,21 @@ describe("listScripts", () => {
215216
]);
216217
});
217218
});
219+
220+
describe("getScriptPath", () => {
221+
test("uses POSIX separators for POSIX workspace paths", () => {
222+
const workspacePath = "/home/user/workspace";
223+
const scriptName = "test.sh";
224+
// Explicitly check for forward slashes regardless of host OS
225+
const expected = "/home/user/workspace/.cmux/scripts/test.sh";
226+
expect(getScriptPath(workspacePath, scriptName)).toBe(expected);
227+
});
228+
229+
test("uses host separators (default) for Windows workspace paths", () => {
230+
const workspacePath = "C:\\Users\\user\\workspace";
231+
const scriptName = "test.bat";
232+
// Should use path.join, which depends on the host OS running the test
233+
const expected = path.join(workspacePath, ".cmux", "scripts", scriptName);
234+
expect(getScriptPath(workspacePath, scriptName)).toBe(expected);
235+
});
236+
});

src/utils/scripts/discovery.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async function discoverScriptsInternal(
8989
runtime: Runtime,
9090
workspacePath: string
9191
): Promise<ScriptInfo[]> {
92-
const scriptsDir = path.join(workspacePath, ".cmux", "scripts");
92+
const scriptsDir = getScriptsDir(workspacePath);
9393
// Unique separator unlikely to appear in filenames or output
9494
const separator = ":::MUX_SCRIPT_START:::";
9595

@@ -212,14 +212,37 @@ function extractDescriptionFromContent(content: string): string | undefined {
212212
return undefined;
213213
}
214214

215+
216+
/**
217+
* Join paths respecting the workspace path style (POSIX vs Windows).
218+
* On Windows, path.join converts everything to backslashes.
219+
* If workspacePath looks like POSIX (has forward slashes, no backslashes), use path.posix.
220+
*/
221+
function joinWorkspacePath(workspacePath: string, ...parts: string[]): string {
222+
const isPosix = workspacePath.includes("/") && !workspacePath.includes("\\");
223+
if (isPosix) {
224+
return path.posix.join(workspacePath, ...parts);
225+
}
226+
return path.join(workspacePath, ...parts);
227+
}
228+
229+
/**
230+
* Get the scripts directory path
231+
* @param workspacePath - Path to the workspace directory
232+
* @returns Path to scripts directory
233+
*/
234+
export function getScriptsDir(workspacePath: string): string {
235+
return joinWorkspacePath(workspacePath, ".cmux", "scripts");
236+
}
237+
215238
/**
216239
* Get the full path to a script
217240
* @param workspacePath - Path to the workspace directory
218241
* @param scriptName - Name of the script file
219242
* @returns Full path to script
220243
*/
221244
export function getScriptPath(workspacePath: string, scriptName: string): string {
222-
return path.join(workspacePath, ".cmux", "scripts", scriptName);
245+
return joinWorkspacePath(workspacePath, ".cmux", "scripts", scriptName);
223246
}
224247

225248
/**

0 commit comments

Comments
 (0)