From a086a81f18df54ac369a2d238b91a379a8dfe147 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 2 Jan 2026 04:21:31 +0000 Subject: [PATCH 1/5] fix: make command chaining examples shell-aware for Windows compatibility Addresses Issue #10352 where Roo Code generates Unix-style command chaining (&&) even on Windows systems using PowerShell or cmd.exe. Changes: - Add getCommandChainOperator() to detect the user shell and return the appropriate command chaining syntax: - Unix shells (bash, zsh, etc.): && - PowerShell: ; - cmd.exe: & - Update getRulesSection() to use shell-specific chaining in examples - Add informative note for non-Unix shells about different syntaxes - Add comprehensive tests for shell detection and command chaining --- src/core/prompts/__tests__/sections.spec.ts | 80 ++++++++++++++++++++- src/core/prompts/sections/rules.ts | 53 +++++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/src/core/prompts/__tests__/sections.spec.ts b/src/core/prompts/__tests__/sections.spec.ts index d8a002d8f52..b56ca4c311e 100644 --- a/src/core/prompts/__tests__/sections.spec.ts +++ b/src/core/prompts/__tests__/sections.spec.ts @@ -1,7 +1,8 @@ import { addCustomInstructions } from "../sections/custom-instructions" import { getCapabilitiesSection } from "../sections/capabilities" -import { getRulesSection } from "../sections/rules" +import { getRulesSection, getCommandChainOperator } from "../sections/rules" import { McpHub } from "../../../services/mcp/McpHub" +import * as shellUtils from "../../../utils/shell" describe("addCustomInstructions", () => { it("adds vscode language to custom instructions", async () => { @@ -114,3 +115,80 @@ describe("getRulesSection", () => { expect(result).not.toContain("Never reveal the vendor or company") }) }) + +describe("getCommandChainOperator", () => { + it("returns && for bash shell", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash") + expect(getCommandChainOperator()).toBe("&&") + }) + + it("returns && for zsh shell", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/zsh") + expect(getCommandChainOperator()).toBe("&&") + }) + + it("returns ; for PowerShell", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + expect(getCommandChainOperator()).toBe(";") + }) + + it("returns ; for PowerShell Core (pwsh)", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Program Files\\PowerShell\\7\\pwsh.exe") + expect(getCommandChainOperator()).toBe(";") + }) + + it("returns & for cmd.exe", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe") + expect(getCommandChainOperator()).toBe("&") + }) + + it("returns && for Git Bash on Windows", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Program Files\\Git\\bin\\bash.exe") + expect(getCommandChainOperator()).toBe("&&") + }) + + it("returns && for WSL bash", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash") + expect(getCommandChainOperator()).toBe("&&") + }) +}) + +describe("getRulesSection shell-aware command chaining", () => { + const cwd = "/test/path" + + afterEach(() => { + vi.restoreAllMocks() + }) + + it("uses && for Unix shells in command chaining example", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash") + const result = getRulesSection(cwd) + + expect(result).toContain("cd (path to project) && (command") + expect(result).not.toContain("cd (path to project) ; (command") + expect(result).not.toContain("cd (path to project) & (command") + }) + + it("uses ; for PowerShell in command chaining example", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + const result = getRulesSection(cwd) + + expect(result).toContain("cd (path to project) ; (command") + expect(result).toContain("Note: Using `;` for PowerShell command chaining") + }) + + it("uses & for cmd.exe in command chaining example", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe") + const result = getRulesSection(cwd) + + expect(result).toContain("cd (path to project) & (command") + expect(result).toContain("Note: Using `&` for cmd.exe command chaining") + }) + + it("does not include note for Unix shells", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/zsh") + const result = getRulesSection(cwd) + + expect(result).not.toContain("Note: Using") + }) +}) diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 20f08970227..bf59ad66b37 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -1,6 +1,53 @@ import type { SystemPromptSettings } from "../types" import { getEffectiveProtocol, isNativeProtocol } from "@roo-code/types" +import { getShell } from "../../../utils/shell" + +/** + * Returns the appropriate command chaining operator based on the user's shell. + * - Unix shells (bash, zsh, etc.): `&&` (run next command only if previous succeeds) + * - PowerShell: `;` (semicolon for command separation) + * - cmd.exe: `&` (single ampersand for command chaining) + * @internal Exported for testing purposes + */ +export function getCommandChainOperator(): string { + const shell = getShell().toLowerCase() + + // Check for PowerShell (both Windows PowerShell and PowerShell Core) + if (shell.includes("powershell") || shell.includes("pwsh")) { + return ";" + } + + // Check for cmd.exe + if (shell.includes("cmd.exe")) { + return "&" + } + + // Default to Unix-style && for bash, zsh, sh, and other shells + // This also covers Git Bash, WSL, and other Unix-like environments on Windows + return "&&" +} + +/** + * Returns a shell-specific note about command chaining syntax for different platforms. + */ +function getCommandChainNote(): string { + const shell = getShell().toLowerCase() + + // Check for PowerShell + if (shell.includes("powershell") || shell.includes("pwsh")) { + return "Note: Using `;` for PowerShell command chaining. For bash/zsh use `&&`, for cmd.exe use `&`." + } + + // Check for cmd.exe + if (shell.includes("cmd.exe")) { + return "Note: Using `&` for cmd.exe command chaining. For bash/zsh use `&&`, for PowerShell use `;`." + } + + // Unix shells + return "" +} + function getVendorConfidentialitySection(): string { return ` @@ -20,6 +67,10 @@ export function getRulesSection(cwd: string, settings?: SystemPromptSettings): s // Determine whether to use XML tool references based on protocol const effectiveProtocol = getEffectiveProtocol(settings?.toolProtocol) + // Get shell-appropriate command chaining operator + const chainOp = getCommandChainOperator() + const chainNote = getCommandChainNote() + return `==== RULES @@ -28,7 +79,7 @@ RULES - All file paths must be relative to this directory. However, commands may change directories in terminals, so respect working directory specified by the response to ${isNativeProtocol(effectiveProtocol) ? "execute_command" : ""}. - You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path. - Do not use the ~ character or $HOME to refer to the home directory. -- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. +- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory ${chainOp} then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) ${chainOp} (command, in this case npm install)\`.${chainNote ? ` ${chainNote}` : ""} - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. * For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$" From b6ba2f940eed5f965689e6d765471db144ebe19b Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 2 Jan 2026 07:17:21 +0000 Subject: [PATCH 2/5] feat: add Unix utility guidance for Windows shells Addresses feedback from issue #10352 about sed and other Unix-specific utilities being suggested on Windows. The system prompt now includes guidance for PowerShell and cmd.exe users to use native alternatives: PowerShell: - Select-String instead of grep - Get-Content instead of cat - Remove-Item instead of rm - Copy-Item instead of cp - Move-Item instead of mv - -replace operator or [regex] instead of sed cmd.exe: - type instead of cat - del instead of rm - copy instead of cp - move instead of mv - find/findstr instead of grep --- src/core/prompts/__tests__/sections.spec.ts | 31 +++++++++++++++++++++ src/core/prompts/sections/rules.ts | 6 ++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/core/prompts/__tests__/sections.spec.ts b/src/core/prompts/__tests__/sections.spec.ts index b56ca4c311e..5277bb28882 100644 --- a/src/core/prompts/__tests__/sections.spec.ts +++ b/src/core/prompts/__tests__/sections.spec.ts @@ -185,6 +185,37 @@ describe("getRulesSection shell-aware command chaining", () => { expect(result).toContain("Note: Using `&` for cmd.exe command chaining") }) + it("includes Unix utility guidance for PowerShell", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + const result = getRulesSection(cwd) + + expect(result).toContain("IMPORTANT: When using PowerShell, avoid Unix-specific utilities") + expect(result).toContain("`sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`") + expect(result).toContain("`Select-String` for grep") + expect(result).toContain("`Get-Content` for cat") + expect(result).toContain("PowerShell's `-replace` operator") + }) + + it("includes Unix utility guidance for cmd.exe", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe") + const result = getRulesSection(cwd) + + expect(result).toContain("IMPORTANT: When using cmd.exe, avoid Unix-specific utilities") + expect(result).toContain("`sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`") + expect(result).toContain("`type` for cat") + expect(result).toContain("`del` for rm") + expect(result).toContain("`find`/`findstr` for grep") + }) + + it("does not include Unix utility guidance for Unix shells", () => { + vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash") + const result = getRulesSection(cwd) + + expect(result).not.toContain("IMPORTANT: When using PowerShell") + expect(result).not.toContain("IMPORTANT: When using cmd.exe") + expect(result).not.toContain("`Select-String` for grep") + }) + it("does not include note for Unix shells", () => { vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/zsh") const result = getRulesSection(cwd) diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index bf59ad66b37..84fc5bd3be0 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -29,19 +29,19 @@ export function getCommandChainOperator(): string { } /** - * Returns a shell-specific note about command chaining syntax for different platforms. + * Returns a shell-specific note about command chaining syntax and platform-specific utilities. */ function getCommandChainNote(): string { const shell = getShell().toLowerCase() // Check for PowerShell if (shell.includes("powershell") || shell.includes("pwsh")) { - return "Note: Using `;` for PowerShell command chaining. For bash/zsh use `&&`, for cmd.exe use `&`." + return "Note: Using `;` for PowerShell command chaining. For bash/zsh use `&&`, for cmd.exe use `&`. IMPORTANT: When using PowerShell, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Instead use PowerShell equivalents: `Select-String` for grep, `Get-Content` for cat, `Remove-Item` for rm, `Copy-Item` for cp, `Move-Item` for mv, and PowerShell's `-replace` operator or `[regex]` for sed." } // Check for cmd.exe if (shell.includes("cmd.exe")) { - return "Note: Using `&` for cmd.exe command chaining. For bash/zsh use `&&`, for PowerShell use `;`." + return "Note: Using `&` for cmd.exe command chaining. For bash/zsh use `&&`, for PowerShell use `;`. IMPORTANT: When using cmd.exe, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Use built-in commands like `type` for cat, `del` for rm, `copy` for cp, `move` for mv, `find`/`findstr` for grep, or consider using PowerShell commands instead." } // Unix shells From af405adb145b00a191a501e5c3771f103d6f8c36 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Wed, 7 Jan 2026 16:51:53 -0700 Subject: [PATCH 3/5] Apply suggestion from @roomote[bot] Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com> --- src/core/prompts/sections/rules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 84fc5bd3be0..79a213b684d 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -20,7 +20,7 @@ export function getCommandChainOperator(): string { // Check for cmd.exe if (shell.includes("cmd.exe")) { - return "&" + return "&&" } // Default to Unix-style && for bash, zsh, sh, and other shells From b467dd32e331f7178883a360adaf50f1b87776f9 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Wed, 7 Jan 2026 17:09:41 -0700 Subject: [PATCH 4/5] fix: use && for cmd.exe to preserve conditional execution semantics - Update getCommandChainOperator() to return && for cmd.exe (already done) - Update getCommandChainNote() to document && instead of & for cmd.exe - Update JSDoc to reflect cmd.exe uses && for conditional execution - Update tests to expect && for cmd.exe cmd.exe supports && for conditional execution (run next command only if previous succeeds), which provides the same semantics as Unix shells. --- src/core/prompts/__tests__/sections.spec.ts | 22 +++++++++++++-------- src/core/prompts/sections/rules.ts | 6 +++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/core/prompts/__tests__/sections.spec.ts b/src/core/prompts/__tests__/sections.spec.ts index 5277bb28882..011b279698e 100644 --- a/src/core/prompts/__tests__/sections.spec.ts +++ b/src/core/prompts/__tests__/sections.spec.ts @@ -128,7 +128,9 @@ describe("getCommandChainOperator", () => { }) it("returns ; for PowerShell", () => { - vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + vi.spyOn(shellUtils, "getShell").mockReturnValue( + "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + ) expect(getCommandChainOperator()).toBe(";") }) @@ -137,9 +139,9 @@ describe("getCommandChainOperator", () => { expect(getCommandChainOperator()).toBe(";") }) - it("returns & for cmd.exe", () => { + it("returns && for cmd.exe", () => { vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe") - expect(getCommandChainOperator()).toBe("&") + expect(getCommandChainOperator()).toBe("&&") }) it("returns && for Git Bash on Windows", () => { @@ -170,23 +172,27 @@ describe("getRulesSection shell-aware command chaining", () => { }) it("uses ; for PowerShell in command chaining example", () => { - vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + vi.spyOn(shellUtils, "getShell").mockReturnValue( + "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + ) const result = getRulesSection(cwd) expect(result).toContain("cd (path to project) ; (command") expect(result).toContain("Note: Using `;` for PowerShell command chaining") }) - it("uses & for cmd.exe in command chaining example", () => { + it("uses && for cmd.exe in command chaining example", () => { vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe") const result = getRulesSection(cwd) - expect(result).toContain("cd (path to project) & (command") - expect(result).toContain("Note: Using `&` for cmd.exe command chaining") + expect(result).toContain("cd (path to project) && (command") + expect(result).toContain("Note: Using `&&` for cmd.exe command chaining") }) it("includes Unix utility guidance for PowerShell", () => { - vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + vi.spyOn(shellUtils, "getShell").mockReturnValue( + "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + ) const result = getRulesSection(cwd) expect(result).toContain("IMPORTANT: When using PowerShell, avoid Unix-specific utilities") diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 79a213b684d..2178d9f3786 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -7,7 +7,7 @@ import { getShell } from "../../../utils/shell" * Returns the appropriate command chaining operator based on the user's shell. * - Unix shells (bash, zsh, etc.): `&&` (run next command only if previous succeeds) * - PowerShell: `;` (semicolon for command separation) - * - cmd.exe: `&` (single ampersand for command chaining) + * - cmd.exe: `&&` (conditional execution, same as Unix) * @internal Exported for testing purposes */ export function getCommandChainOperator(): string { @@ -20,7 +20,7 @@ export function getCommandChainOperator(): string { // Check for cmd.exe if (shell.includes("cmd.exe")) { - return "&&" + return "&&" } // Default to Unix-style && for bash, zsh, sh, and other shells @@ -41,7 +41,7 @@ function getCommandChainNote(): string { // Check for cmd.exe if (shell.includes("cmd.exe")) { - return "Note: Using `&` for cmd.exe command chaining. For bash/zsh use `&&`, for PowerShell use `;`. IMPORTANT: When using cmd.exe, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Use built-in commands like `type` for cat, `del` for rm, `copy` for cp, `move` for mv, `find`/`findstr` for grep, or consider using PowerShell commands instead." + return "Note: Using `&&` for cmd.exe command chaining (conditional execution). For bash/zsh use `&&`, for PowerShell use `;`. IMPORTANT: When using cmd.exe, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Use built-in commands like `type` for cat, `del` for rm, `copy` for cp, `move` for mv, `find`/`findstr` for grep, or consider using PowerShell commands instead." } // Unix shells From 04b9782e037140d8aee739c245522df1dd8e5c99 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 8 Jan 2026 00:30:01 +0000 Subject: [PATCH 5/5] fix: update PowerShell note to use && for cmd.exe reference --- src/core/prompts/sections/rules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 2178d9f3786..800fb430efb 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -36,7 +36,7 @@ function getCommandChainNote(): string { // Check for PowerShell if (shell.includes("powershell") || shell.includes("pwsh")) { - return "Note: Using `;` for PowerShell command chaining. For bash/zsh use `&&`, for cmd.exe use `&`. IMPORTANT: When using PowerShell, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Instead use PowerShell equivalents: `Select-String` for grep, `Get-Content` for cat, `Remove-Item` for rm, `Copy-Item` for cp, `Move-Item` for mv, and PowerShell's `-replace` operator or `[regex]` for sed." + return "Note: Using `;` for PowerShell command chaining. For bash/zsh use `&&`, for cmd.exe use `&&`. IMPORTANT: When using PowerShell, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Instead use PowerShell equivalents: `Select-String` for grep, `Get-Content` for cat, `Remove-Item` for rm, `Copy-Item` for cp, `Move-Item` for mv, and PowerShell's `-replace` operator or `[regex]` for sed." } // Check for cmd.exe