From 47d60a74d30f9c562a3e5c4e01b665f0399791bd Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 13 May 2026 22:47:37 +0900 Subject: [PATCH] fix(non-interactive-env): honor Windows ComSpec shell Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/hooks/non-interactive-env/index.test.ts | 25 +++++++++++++ .../non-interactive-env-hook.ts | 36 ++++++++++++------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/hooks/non-interactive-env/index.test.ts b/src/hooks/non-interactive-env/index.test.ts index 7f34651c5..0582a3853 100644 --- a/src/hooks/non-interactive-env/index.test.ts +++ b/src/hooks/non-interactive-env/index.test.ts @@ -13,6 +13,7 @@ describe("non-interactive-env hook", () => { SHELL: process.env.SHELL, PSModulePath: process.env.PSModulePath, MSYSTEM: process.env.MSYSTEM, + ComSpec: process.env.ComSpec, CI: process.env.CI, OPENCODE_NON_INTERACTIVE: process.env.OPENCODE_NON_INTERACTIVE, } @@ -298,6 +299,30 @@ describe("non-interactive-env hook", () => { expect(cmd).not.toContain("export ") }) + test("#given Windows ComSpec=pwsh.exe without SHELL #when bash tool git command executes #then uses powershell syntax", async () => { + delete process.env.SHELL + delete process.env.MSYSTEM + process.env.ComSpec = "C:\\Program Files\\PowerShell\\7\\pwsh.exe" + process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules" + Object.defineProperty(process, "platform", { value: "win32" }) + + const hook = createNonInteractiveEnvHook(mockCtx) + const output: { args: Record; message?: string } = { + args: { command: "git status" }, + } + + await hook["tool.execute.before"]( + { tool: "bash", sessionID: "test", callID: "1" }, + output + ) + + const cmd = output.args.command as string + expect(cmd).toStartWith("$env:") + expect(cmd).toContain("; git status") + expect(cmd).not.toContain("set ") + expect(cmd).not.toContain("export ") + }) + test("#given Windows SHELL=pwsh.exe #when bash tool git command executes #then uses powershell syntax", async () => { process.env.SHELL = "C:\\Program Files\\PowerShell\\7\\pwsh.exe" delete process.env.MSYSTEM diff --git a/src/hooks/non-interactive-env/non-interactive-env-hook.ts b/src/hooks/non-interactive-env/non-interactive-env-hook.ts index 7fc034110..6fc42aea9 100644 --- a/src/hooks/non-interactive-env/non-interactive-env-hook.ts +++ b/src/hooks/non-interactive-env/non-interactive-env-hook.ts @@ -20,24 +20,36 @@ function detectBannedCommand(command: string): string | undefined { return undefined } +function detectWindowsShellType(shellPath: string | undefined): ShellType | undefined { + if (!shellPath) { + return undefined + } + + const shellName = shellPath.replace(/\\/g, "/").split("/").pop()?.toLowerCase() + if (shellName === "cmd" || shellName === "cmd.exe") { + return "cmd" + } + if ( + shellName === "powershell" || + shellName === "powershell.exe" || + shellName === "pwsh" || + shellName === "pwsh.exe" + ) { + return "powershell" + } + return undefined +} + function detectCommandShellType(): ShellType { if (process.platform === "win32" && process.env.SHELL) { - const shellName = process.env.SHELL.replace(/\\/g, "/").split("/").pop()?.toLowerCase() - if (shellName === "cmd" || shellName === "cmd.exe") { - return "cmd" - } - if ( - shellName === "powershell" || - shellName === "powershell.exe" || - shellName === "pwsh" || - shellName === "pwsh.exe" - ) { - return "powershell" + const shellType = detectWindowsShellType(process.env.SHELL) + if (shellType) { + return shellType } } if (process.platform === "win32" && !process.env.SHELL && !process.env.MSYSTEM) { - return "cmd" + return detectWindowsShellType(process.env.ComSpec) ?? "cmd" } return detectShellType()