From e93cef3fef8b1e43572d9e681d6a78310ad05c5c Mon Sep 17 00:00:00 2001 From: Jarvis Date: Sat, 7 Mar 2026 01:04:25 +0800 Subject: [PATCH] Handle missing bash in terminal tool shell selection --- .../implementations/runTerminalCommand.ts | 23 ++++++++++++++++--- .../runTerminalCommand.vitest.ts | 21 +++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/core/tools/implementations/runTerminalCommand.ts b/core/tools/implementations/runTerminalCommand.ts index 351ee75cae6..f25e5a78c27 100644 --- a/core/tools/implementations/runTerminalCommand.ts +++ b/core/tools/implementations/runTerminalCommand.ts @@ -1,5 +1,6 @@ import iconv from "iconv-lite"; import childProcess from "node:child_process"; +import fs from "node:fs"; import os from "node:os"; import { ContinueError, ContinueErrorReason } from "../../util/errors"; // Automatically decode the buffer according to the platform to avoid garbled Chinese @@ -26,9 +27,25 @@ function getShellCommand(command: string): { shell: string; args: string[] } { args: ["-NoLogo", "-ExecutionPolicy", "Bypass", "-Command", command], }; } else { - // Unix/macOS: Use login shell to source .bashrc/.zshrc etc. - const userShell = process.env.SHELL || "/bin/bash"; - return { shell: userShell, args: ["-l", "-c", command] }; + // Unix/macOS: prefer configured shell, but gracefully fall back when unavailable + // (e.g. minimal dev containers without /bin/bash). + const configuredShell = process.env.SHELL; + const unixFallbacks = ["/bin/bash", "/bin/sh", "/bin/ash"]; + const shellCandidates = configuredShell + ? [configuredShell, ...unixFallbacks] + : unixFallbacks; + + const shell = + shellCandidates.find((candidate) => { + if (!candidate) return false; + if (!candidate.startsWith("/")) { + // Non-absolute shells are resolved by PATH at spawn time. + return true; + } + return fs.existsSync(candidate); + }) ?? "/bin/sh"; + + return { shell, args: ["-l", "-c", command] }; } } diff --git a/core/tools/implementations/runTerminalCommand.vitest.ts b/core/tools/implementations/runTerminalCommand.vitest.ts index ea502f7c015..b4941e394e6 100644 --- a/core/tools/implementations/runTerminalCommand.vitest.ts +++ b/core/tools/implementations/runTerminalCommand.vitest.ts @@ -142,6 +142,27 @@ describe("runTerminalCommandImpl", () => { expect(result[0].status).toBe("Command completed"); }); + it("should fall back to an available shell when SHELL is invalid", async () => { + if (process.platform === "win32") { + return; + } + + const originalShell = process.env.SHELL; + process.env.SHELL = "/definitely-not-a-real-shell"; + + try { + const result = await runTerminalCommandImpl( + { command: `node -e "console.log('fallback shell works')"` }, + createMockExtras(), + ); + + expect(result[0].status).toBe("Command completed"); + expect(result[0].content).toContain("fallback shell works"); + } finally { + process.env.SHELL = originalShell; + } + }); + it("should stream output when onPartialOutput is provided", async () => { // This test uses Node to create a command that outputs data incrementally const command = `node -e "