From acb1b7ba4e7075380c36821692b479a917f0cb9e Mon Sep 17 00:00:00 2001 From: NewdlDewdl Date: Sun, 8 Mar 2026 14:35:25 -0500 Subject: [PATCH 1/2] fix(exec): avoid shell startup-file env overrides --- src/agents/shell-utils.test.ts | 19 +++++++++++++++---- src/agents/shell-utils.ts | 20 +++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/agents/shell-utils.test.ts b/src/agents/shell-utils.test.ts index 9716fb73c8d..40e745063f2 100644 --- a/src/agents/shell-utils.test.ts +++ b/src/agents/shell-utils.test.ts @@ -51,28 +51,39 @@ describe("getShellConfig", () => { it("prefers bash when fish is default and bash is on PATH", () => { const binDir = createTempCommandDir(tempDirs, [{ name: "bash" }]); process.env.PATH = binDir; - const { shell } = getShellConfig(); + const { shell, args } = getShellConfig(); expect(shell).toBe(path.join(binDir, "bash")); + expect(args).toEqual(["--noprofile", "--norc", "-c"]); }); it("falls back to sh when fish is default and bash is missing", () => { const binDir = createTempCommandDir(tempDirs, [{ name: "sh" }]); process.env.PATH = binDir; - const { shell } = getShellConfig(); + const { shell, args } = getShellConfig(); expect(shell).toBe(path.join(binDir, "sh")); + expect(args).toEqual(["-c"]); }); it("falls back to env shell when fish is default and no sh is available", () => { process.env.PATH = ""; - const { shell } = getShellConfig(); + const { shell, args } = getShellConfig(); expect(shell).toBe("/usr/bin/fish"); + expect(args).toEqual(["-c"]); + }); + + it("uses zsh no-rc mode to avoid startup-file env overrides", () => { + process.env.SHELL = "/bin/zsh"; + const { shell, args } = getShellConfig(); + expect(shell).toBe("/bin/zsh"); + expect(args).toEqual(["-f", "-c"]); }); it("uses sh when SHELL is unset", () => { delete process.env.SHELL; process.env.PATH = ""; - const { shell } = getShellConfig(); + const { shell, args } = getShellConfig(); expect(shell).toBe("sh"); + expect(args).toEqual(["-c"]); }); }); diff --git a/src/agents/shell-utils.ts b/src/agents/shell-utils.ts index a4a5dbc115a..f9be1b368c0 100644 --- a/src/agents/shell-utils.ts +++ b/src/agents/shell-utils.ts @@ -39,6 +39,20 @@ export function resolvePowerShellPath(): string { return "powershell.exe"; } +function resolvePosixShellArgs(shellPath: string): string[] { + const shellName = normalizeShellName(shellPath); + + // Keep exec commands deterministic: avoid user startup files overriding inherited + // daemon environment variables (for example launchd-provided secrets on macOS). + if (shellName === "zsh") { + return ["-f", "-c"]; + } + if (shellName === "bash") { + return ["--noprofile", "--norc", "-c"]; + } + return ["-c"]; +} + export function getShellConfig(): { shell: string; args: string[] } { if (process.platform === "win32") { // Use PowerShell instead of cmd.exe on Windows. @@ -58,15 +72,15 @@ export function getShellConfig(): { shell: string; args: string[] } { if (shellName === "fish") { const bash = resolveShellFromPath("bash"); if (bash) { - return { shell: bash, args: ["-c"] }; + return { shell: bash, args: resolvePosixShellArgs(bash) }; } const sh = resolveShellFromPath("sh"); if (sh) { - return { shell: sh, args: ["-c"] }; + return { shell: sh, args: resolvePosixShellArgs(sh) }; } } const shell = envShell && envShell.length > 0 ? envShell : "sh"; - return { shell, args: ["-c"] }; + return { shell, args: resolvePosixShellArgs(shell) }; } export function resolveShellFromPath(name: string): string | undefined { From b831c7bce7411ce65f8a8bdbd0be712d56c69ecd Mon Sep 17 00:00:00 2001 From: NewdlDewdl Date: Sun, 8 Mar 2026 16:24:33 -0500 Subject: [PATCH 2/2] fix(exec): suppress fish startup config in fallback shell --- src/agents/shell-utils.test.ts | 2 +- src/agents/shell-utils.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/agents/shell-utils.test.ts b/src/agents/shell-utils.test.ts index 40e745063f2..99dca3fba19 100644 --- a/src/agents/shell-utils.test.ts +++ b/src/agents/shell-utils.test.ts @@ -68,7 +68,7 @@ describe("getShellConfig", () => { process.env.PATH = ""; const { shell, args } = getShellConfig(); expect(shell).toBe("/usr/bin/fish"); - expect(args).toEqual(["-c"]); + expect(args).toEqual(["--no-config", "-c"]); }); it("uses zsh no-rc mode to avoid startup-file env overrides", () => { diff --git a/src/agents/shell-utils.ts b/src/agents/shell-utils.ts index f9be1b368c0..e23a2bf600d 100644 --- a/src/agents/shell-utils.ts +++ b/src/agents/shell-utils.ts @@ -50,6 +50,9 @@ function resolvePosixShellArgs(shellPath: string): string[] { if (shellName === "bash") { return ["--noprofile", "--norc", "-c"]; } + if (shellName === "fish") { + return ["--no-config", "-c"]; + } return ["-c"]; }