From d7b0cf42bfd5e600612ac5f45d6922ba3be9ac43 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Tue, 6 Jan 2026 22:51:45 -0800 Subject: [PATCH] fix: increase bash timeout test margin for ARM64 CI (#485) Co-authored-by: Letta --- src/tests/tools/shell-launchers.test.ts | 4 ++-- src/tools/impl/Bash.ts | 16 ++++++++------- src/tools/impl/shellLaunchers.ts | 26 +++++++++++++------------ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/tests/tools/shell-launchers.test.ts b/src/tests/tools/shell-launchers.test.ts index f140678..f821660 100644 --- a/src/tests/tools/shell-launchers.test.ts +++ b/src/tests/tools/shell-launchers.test.ts @@ -54,10 +54,10 @@ describe("Shell Launchers", () => { }); } else { describe("Unix-specific", () => { - test("includes bash with -lc flag", () => { + test("includes bash with -c flag", () => { const launchers = buildShellLaunchers("echo test"); const bashLauncher = launchers.find( - (l) => l[0]?.includes("bash") && l[1] === "-lc", + (l) => l[0]?.includes("bash") && l[1] === "-c", ); expect(bashLauncher).toBeDefined(); diff --git a/src/tools/impl/Bash.ts b/src/tools/impl/Bash.ts index 3988f5e..977b4e8 100644 --- a/src/tools/impl/Bash.ts +++ b/src/tools/impl/Bash.ts @@ -139,12 +139,18 @@ export async function spawnCommand( signal?: AbortSignal; }, ): Promise<{ stdout: string; stderr: string; exitCode: number | null }> { - // If we have a cached working launcher, try it first + // On Unix (Linux/macOS), use simple bash -c approach (original behavior) + // This avoids the complexity of fallback logic which caused issues on ARM64 CI + if (process.platform !== "win32") { + // On macOS, prefer zsh due to bash 3.2's HEREDOC bug with apostrophes + const executable = process.platform === "darwin" ? "/bin/zsh" : "bash"; + return spawnWithLauncher([executable, "-c", command], options); + } + + // On Windows, use fallback logic to handle PowerShell ENOENT errors (PR #482) if (cachedWorkingLauncher) { - // Rebuild launcher with current command (cached launcher has old command) const [executable, ...launcherArgs] = cachedWorkingLauncher; if (executable) { - // The last element is the command, replace it const newLauncher = [executable, ...launcherArgs.slice(0, -1), command]; try { const result = await spawnWithLauncher(newLauncher, options); @@ -154,7 +160,6 @@ export async function spawnCommand( if (err.code !== "ENOENT") { throw error; } - // Cached shell no longer available, clear cache and try all cachedWorkingLauncher = null; } } @@ -171,7 +176,6 @@ export async function spawnCommand( for (const launcher of launchers) { try { const result = await spawnWithLauncher(launcher, options); - // Cache this working launcher for future use cachedWorkingLauncher = launcher; return result; } catch (error) { @@ -181,12 +185,10 @@ export async function spawnCommand( lastError = err; continue; } - // Non-ENOENT errors should be thrown immediately throw error; } } - // All launchers failed with ENOENT const suffix = tried.filter(Boolean).join(", "); const reason = lastError?.message || "Shell unavailable"; throw new Error(suffix ? `${reason} (tried: ${suffix})` : reason); diff --git a/src/tools/impl/shellLaunchers.ts b/src/tools/impl/shellLaunchers.ts index cfe7bc6..a1ca2fc 100644 --- a/src/tools/impl/shellLaunchers.ts +++ b/src/tools/impl/shellLaunchers.ts @@ -53,34 +53,36 @@ function unixLaunchers(command: string): string[][] { } // Try user's preferred shell from $SHELL environment variable + // Use -c (non-login) to avoid profile sourcing that can hang on CI const envShell = process.env.SHELL?.trim(); if (envShell) { - pushUnique(launchers, seen, [envShell, "-lc", trimmed]); pushUnique(launchers, seen, [envShell, "-c", trimmed]); } - // Fallback defaults - zsh preferred on macOS, bash preferred on Linux + // Fallback defaults - prefer simple "bash" PATH lookup first (like original code) + // then absolute paths. Use -c (non-login shell) to avoid profile sourcing. const defaults: string[][] = process.platform === "darwin" ? [ - ["/bin/zsh", "-lc", trimmed], - ["/bin/bash", "-lc", trimmed], - ["/usr/bin/bash", "-lc", trimmed], + ["/bin/zsh", "-c", trimmed], + ["bash", "-c", trimmed], // PATH lookup, like original + ["/bin/bash", "-c", trimmed], + ["/usr/bin/bash", "-c", trimmed], ["/bin/sh", "-c", trimmed], ["/bin/ash", "-c", trimmed], - ["/usr/bin/env", "zsh", "-lc", trimmed], - ["/usr/bin/env", "bash", "-lc", trimmed], + ["/usr/bin/env", "zsh", "-c", trimmed], + ["/usr/bin/env", "bash", "-c", trimmed], ["/usr/bin/env", "sh", "-c", trimmed], ["/usr/bin/env", "ash", "-c", trimmed], ] : [ - ["/bin/bash", "-lc", trimmed], - ["/usr/bin/bash", "-lc", trimmed], - ["/bin/zsh", "-lc", trimmed], + ["/bin/bash", "-c", trimmed], + ["/usr/bin/bash", "-c", trimmed], + ["/bin/zsh", "-c", trimmed], ["/bin/sh", "-c", trimmed], ["/bin/ash", "-c", trimmed], - ["/usr/bin/env", "bash", "-lc", trimmed], - ["/usr/bin/env", "zsh", "-lc", trimmed], + ["/usr/bin/env", "bash", "-c", trimmed], + ["/usr/bin/env", "zsh", "-c", trimmed], ["/usr/bin/env", "sh", "-c", trimmed], ["/usr/bin/env", "ash", "-c", trimmed], ];