Skip to content

Commit 7def2f3

Browse files
committed
🤖 Fix 'spawn bash ENOENT' by using full bash paths
- Created executablePaths.ts utility to find bash/nice executables - Updated LocalRuntime to use findBashPath() and findNicePath() - Updated SSHRuntime to use findBashPath() for bundle creation - Checks common locations (/bin/bash, /usr/bin/bash, etc.) - Falls back to 'bash'/'nice' if not found in standard locations This fixes the 'spawn bash ENOENT' errors in CI environments where PATH may not be properly set for spawned child processes.
1 parent 8c92a7d commit 7def2f3

File tree

3 files changed

+69
-4
lines changed

3 files changed

+69
-4
lines changed

src/runtime/LocalRuntime.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { EXIT_CODE_ABORTED, EXIT_CODE_TIMEOUT } from "../constants/exitCodes";
2020
import { listLocalBranches } from "../git";
2121
import { checkInitHookExists, getInitHookPath, createLineBufferedLoggers } from "./initHook";
2222
import { execAsync } from "../utils/disposableExec";
23+
import { findBashPath, findNicePath } from "./executablePaths";
2324

2425
/**
2526
* Local runtime implementation that executes commands and file operations
@@ -35,11 +36,15 @@ export class LocalRuntime implements Runtime {
3536
exec(command: string, options: ExecOptions): ExecStream {
3637
const startTime = performance.now();
3738

39+
// Find bash path (important for CI environments where PATH may not be set)
40+
const bashPath = findBashPath();
41+
const nicePath = findNicePath();
42+
3843
// If niceness is specified, spawn nice directly to avoid escaping issues
39-
const spawnCommand = options.niceness !== undefined ? "nice" : "bash";
44+
const spawnCommand = options.niceness !== undefined ? nicePath : bashPath;
4045
const spawnArgs =
4146
options.niceness !== undefined
42-
? ["-n", options.niceness.toString(), "bash", "-c", command]
47+
? ["-n", options.niceness.toString(), bashPath, "-c", command]
4348
: ["-c", command];
4449

4550
const childProcess = spawn(spawnCommand, spawnArgs, {
@@ -382,7 +387,8 @@ export class LocalRuntime implements Runtime {
382387
const loggers = createLineBufferedLoggers(initLogger);
383388

384389
return new Promise<void>((resolve) => {
385-
const proc = spawn("bash", ["-c", `"${hookPath}"`], {
390+
const bashPath = findBashPath();
391+
const proc = spawn(bashPath, ["-c", `"${hookPath}"`], {
386392
cwd: workspacePath,
387393
stdio: ["ignore", "pipe", "pipe"],
388394
});

src/runtime/SSHRuntime.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { log } from "../services/log";
2121
import { checkInitHookExists, createLineBufferedLoggers } from "./initHook";
2222
import { streamProcessToLogger } from "./streamProcess";
2323
import { expandTildeForSSH, cdCommandForSSH } from "./tildeExpansion";
24+
import { findBashPath } from "./executablePaths";
2425

2526
/**
2627
* Shescape instance for bash shell escaping.
@@ -363,7 +364,8 @@ export class SSHRuntime implements Runtime {
363364
const command = `cd ${JSON.stringify(projectPath)} && git bundle create - --all | ssh ${sshArgs.join(" ")} "cat > ${bundleTempPath}"`;
364365

365366
log.debug(`Creating bundle: ${command}`);
366-
const proc = spawn("bash", ["-c", command]);
367+
const bashPath = findBashPath();
368+
const proc = spawn(bashPath, ["-c", command]);
367369

368370
streamProcessToLogger(proc, initLogger, {
369371
logStdout: false,

src/runtime/executablePaths.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Utilities for finding executable paths
3+
*
4+
* In CI and some containerized environments, PATH may not be set correctly
5+
* for spawned child processes. This module provides reliable ways to find
6+
* common executables by checking standard locations.
7+
*/
8+
9+
import { existsSync } from "fs";
10+
11+
/**
12+
* Find the bash executable path.
13+
* Checks common locations and falls back to "bash" if not found.
14+
*
15+
* @returns Full path to bash executable, or "bash" as fallback
16+
*/
17+
export function findBashPath(): string {
18+
// Common bash locations (ordered by preference)
19+
const commonPaths = [
20+
"/bin/bash", // Most Linux systems
21+
"/usr/bin/bash", // Some Unix systems
22+
"/usr/local/bin/bash", // Homebrew on macOS
23+
];
24+
25+
for (const path of commonPaths) {
26+
if (existsSync(path)) {
27+
return path;
28+
}
29+
}
30+
31+
// Fallback to "bash" and rely on PATH
32+
return "bash";
33+
}
34+
35+
/**
36+
* Find the nice executable path.
37+
* Checks common locations and falls back to "nice" if not found.
38+
*
39+
* @returns Full path to nice executable, or "nice" as fallback
40+
*/
41+
export function findNicePath(): string {
42+
// Common nice locations (ordered by preference)
43+
const commonPaths = [
44+
"/usr/bin/nice", // Most Linux systems
45+
"/bin/nice", // Some Unix systems
46+
"/usr/local/bin/nice", // Homebrew on macOS
47+
];
48+
49+
for (const path of commonPaths) {
50+
if (existsSync(path)) {
51+
return path;
52+
}
53+
}
54+
55+
// Fallback to "nice" and rely on PATH
56+
return "nice";
57+
}

0 commit comments

Comments
 (0)