fix: use PowerShell 7 and correct shell detection on Windows (Fixes #11958)#11963
fix: use PowerShell 7 and correct shell detection on Windows (Fixes #11958)#119633aKHP wants to merge 2 commits intoRooCodeInc:mainfrom
Conversation
|
|
||
| this.subprocess = execa({ | ||
| shell: BaseTerminal.getExecaShellPath() || true, | ||
| shell: BaseTerminal.getExecaShellPath() || getShell(), |
There was a problem hiding this comment.
getShell call returns the path only of the shell. So for users that have custom shells like git bash and WSL that have args, the args will be ignored and the shell won't work correctly. So I'm not sure if this is the best approach.
There was a problem hiding this comment.
Great catch regarding custom shells with arguments like WSL! I didn't consider that execa's shell option drops arguments. I will revert this back to true.
However, this brings back the original issue for background tasks: on Windows, shell: true defaults to cmd.exe, which uses the local code page (e.g., GBK) and causes severe mojibake for non-ASCII output.
Since we can't safely use
getShell()here, should we just acceptcmd.exeas the default and recommend users to explicitly set theroo-cline.execaShellPathsetting if they need UTF-8?
There was a problem hiding this comment.
I was looking at that other issue which was also the UTF-8 output. This is what I came up with for run(). It might be incorporated with your changes.
Note: This code changes the logic and forces PowerShell v1 as a fallback. So if you want to keep the original logic where the user has not set a default terminal profile, then it falls back to cmd.exe. You have to check for a null result from getShell() and use a blank commandArgs etc.
Also have to change shell: !isWindows to support custom linux shells so the original logic shell: BaseTerminal.getExecaShellPath() || true is not lost but when is windows is true then shell should be false as powershell is run as a direct command not a shell.
public override async run(command: string) {
this.command = command
try {
this.isHot = true
const isWindows = process.platform === "win32"
let commandToRun: string = command
let commandArgs: string[] = []
if (isWindows) {
// On Windows check user's shell and use PowerShell to support UTF-8 encoding
const userShell = getShell()
const shellName = userShell.toLowerCase()
commandToRun = shellName.includes("powershell") || shellName.includes("pwsh") ? userShell : "powershell"
// Use array format for execa to properly handle paths with spaces
commandArgs = [
"-NoProfile",
"-NonInteractive",
"-Command",
`[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ${command}`,
]
}
let options: Options = {
shell: !isWindows,
cwd: this.terminal.getCurrentWorkingDirectory(),
all: true,
// Ignore stdin to ensure non-interactive mode and prevent hanging
stdin: "ignore",
env: {
...process.env,
// Ensure UTF-8 encoding for Ruby, CocoaPods, etc.
LANG: "en_US.UTF-8",
LC_ALL: "en_US.UTF-8",
NO_COLOR: "1",
},
}
this.subprocess = execa<Options>(commandToRun, commandArgs, options)There's also a small fix in the output decoding to make sure that double byte characters are decoded correctly. Move the decoder out of the loop and set stream to true. It looks like this:
const rawStream = this.subprocess.iterable({ from: "all", preserveNewlines: true })
const decoder = new TextDecoder("utf-8")
// Wrap the stream to ensure all chunks are strings (execa can return Uint8Array)
const stream = (async function* () {
for await (const chunk of rawStream) {
// stream true ensures that if a double byte character is split between two buffer chunks, it will still be rendered correctly rather than appearing as corrupted bytes.
yield typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true })
}
})()
src/utils/shell.ts
Outdated
| // Otherwise, assume legacy Windows PowerShell | ||
| return SHELL_PATHS.POWERSHELL_LEGACY | ||
| // Otherwise, assume modern PowerShell 7 (pwsh.exe) as the default | ||
| return SHELL_PATHS.POWERSHELL_7 |
There was a problem hiding this comment.
PowerShell 7 is not installed by default on Windows so it's better to keep powershell v1 as it will be available.
There was a problem hiding this comment.
You are absolutely right. I overlooked that PS7 is not pre-installed on Windows. I will revert this fallback to POWERSHELL_LEGACY. Since Fix 1 corrects the VS Code config API read, users who do have PS7 configured will now be correctly detected anyway. I'll start on this immediately.
Related GitHub Issue
Closes: #11958
Roo Code Task Context (Optional)
Description
Fixes three shell detection flaws that cause Windows users with PowerShell 7 configured to get legacy PowerShell 5.1 in background execution, resulting in UTF-8 mojibake for non-ASCII output.
Key changes:
TerminalProcess.ts— Fixed incorrect VS Code config API call.getConfiguration("terminal.integrated.defaultProfile").get("windows")always returnednullbecause the key was split at the wrong level. Changed togetConfiguration("terminal.integrated").get<string>("defaultProfile.windows")to match the pattern used inshell.ts.ExecaTerminalProcess.ts— WhenBaseTerminal.getExecaShellPath()is undefined, the fallbackshell: truecauses Node.js/execa to usecmd.exe(legacy codepage on Windows). Changed toshell: getShell(), which resolves to the user's configured shell (e.g.,pwsh.exe).shell.ts— When a PowerShell profile name matches but has no explicitpathorsource, the default wasPOWERSHELL_LEGACY. Changed toPOWERSHELL_7, since VS Code's auto-detected PowerShell 7 profile typically lacks these properties.Test Procedure
shell.spec.ts— 37 tests passing (changed expectation for no-path/no-source PowerShell profile from legacy to PS7)ExecaTerminalProcess.spec.ts— addedgetShellmock, updated shell fallback expectationsTerminalProcess.test.ts— all existing tests passingpnpm check-types— passes across all 14 packagesecho "你好"→ confirm correct UTF-8 output without mojibakePre-Submission Checklist
Screenshots / Videos
No UI changes. This fix affects background terminal shell selection logic only.
Documentation Updates
Additional Notes
The three bugs are independent but closely related — they all contribute to the same user-visible symptom (mojibake on Windows with PS7). Fixing them together in one PR keeps the scope minimal while fully resolving the issue.
Get in Touch
discord @3aKHP
Interactively review PR in Roo Code Cloud