Skip to content

fix: use PowerShell 7 and correct shell detection on Windows (Fixes #11958)#11963

Open
3aKHP wants to merge 2 commits intoRooCodeInc:mainfrom
3aKHP:fix-windows-pwsh-encoding
Open

fix: use PowerShell 7 and correct shell detection on Windows (Fixes #11958)#11963
3aKHP wants to merge 2 commits intoRooCodeInc:mainfrom
3aKHP:fix-windows-pwsh-encoding

Conversation

@3aKHP
Copy link
Copy Markdown

@3aKHP 3aKHP commented Mar 20, 2026

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:

  1. TerminalProcess.ts — Fixed incorrect VS Code config API call. getConfiguration("terminal.integrated.defaultProfile").get("windows") always returned null because the key was split at the wrong level. Changed to getConfiguration("terminal.integrated").get<string>("defaultProfile.windows") to match the pattern used in shell.ts.

  2. ExecaTerminalProcess.ts — When BaseTerminal.getExecaShellPath() is undefined, the fallback shell: true causes Node.js/execa to use cmd.exe (legacy codepage on Windows). Changed to shell: getShell(), which resolves to the user's configured shell (e.g., pwsh.exe).

  3. shell.ts — When a PowerShell profile name matches but has no explicit path or source, the default was POWERSHELL_LEGACY. Changed to POWERSHELL_7, since VS Code's auto-detected PowerShell 7 profile typically lacks these properties.

Test Procedure

  • Updated shell.spec.ts — 37 tests passing (changed expectation for no-path/no-source PowerShell profile from legacy to PS7)
  • Updated ExecaTerminalProcess.spec.ts — added getShell mock, updated shell fallback expectations
  • TerminalProcess.test.ts — all existing tests passing
  • pnpm check-types — passes across all 14 packages
  • Manual verification: Set PowerShell 7 as default VS Code terminal profile with "Disable Terminal Shell Integration" enabled → ask Roo Code to run echo "你好" → confirm correct UTF-8 output without mojibake

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Screenshots / Videos

No UI changes. This fix affects background terminal shell selection logic only.

Documentation Updates

  • No documentation updates are required.

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

@dosubot dosubot bot added size:S This PR changes 10-29 lines, ignoring generated files. bug Something isn't working labels Mar 20, 2026

this.subprocess = execa({
shell: BaseTerminal.getExecaShellPath() || true,
shell: BaseTerminal.getExecaShellPath() || getShell(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 accept cmd.exe as the default and recommend users to explicitly set the roo-cline.execaShellPath setting if they need UTF-8?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 })
				}
			})()

// 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PowerShell 7 is not installed by default on Windows so it's better to keep powershell v1 as it will be available.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dosubot dosubot bot added size:XS This PR changes 0-9 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Mar 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Windows terminal fallback incorrectly defaults to PowerShell 5.1 (Legacy) ignoring PS7 profile, causing UTF-8 encoding issues

2 participants