diff --git a/src/infra/executable-path.test.ts b/src/infra/executable-path.test.ts index 731457ab183..31437cafe49 100644 --- a/src/infra/executable-path.test.ts +++ b/src/infra/executable-path.test.ts @@ -47,4 +47,27 @@ describe("executable path helpers", () => { expect(resolveExecutablePath("runner", { env: { PATH: binDir } })).toBe(pathTool); expect(resolveExecutablePath("missing", { env: { PATH: binDir } })).toBeUndefined(); }); + + it("resolves absolute, home-relative, and Path-cased env executables", async () => { + const base = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-exec-path-")); + const homeDir = path.join(base, "home"); + const binDir = path.join(base, "bin"); + await fs.mkdir(homeDir, { recursive: true }); + await fs.mkdir(binDir, { recursive: true }); + + const homeTool = path.join(homeDir, "home-tool"); + const absoluteTool = path.join(base, "absolute-tool"); + const pathTool = path.join(binDir, "runner"); + await fs.writeFile(homeTool, "#!/bin/sh\nexit 0\n", "utf8"); + await fs.writeFile(absoluteTool, "#!/bin/sh\nexit 0\n", "utf8"); + await fs.writeFile(pathTool, "#!/bin/sh\nexit 0\n", "utf8"); + await fs.chmod(homeTool, 0o755); + await fs.chmod(absoluteTool, 0o755); + await fs.chmod(pathTool, 0o755); + + expect(resolveExecutablePath(absoluteTool)).toBe(absoluteTool); + expect(resolveExecutablePath("~/home-tool", { env: { HOME: homeDir } })).toBe(homeTool); + expect(resolveExecutablePath("runner", { env: { Path: binDir } })).toBe(pathTool); + expect(resolveExecutablePath("~/missing-tool", { env: { HOME: homeDir } })).toBeUndefined(); + }); }); diff --git a/src/infra/executable-path.ts b/src/infra/executable-path.ts index b25231a4a50..bf648c7cb6a 100644 --- a/src/infra/executable-path.ts +++ b/src/infra/executable-path.ts @@ -60,7 +60,9 @@ export function resolveExecutablePath( rawExecutable: string, options?: { cwd?: string; env?: NodeJS.ProcessEnv }, ): string | undefined { - const expanded = rawExecutable.startsWith("~") ? expandHomePrefix(rawExecutable) : rawExecutable; + const expanded = rawExecutable.startsWith("~") + ? expandHomePrefix(rawExecutable, { env: options?.env }) + : rawExecutable; if (expanded.includes("/") || expanded.includes("\\")) { if (path.isAbsolute(expanded)) { return isExecutableFile(expanded) ? expanded : undefined;