Skip to content

fix(shell): add PowerShell UTF-8 command wrapper on Windows#31985

Open
senguangd wants to merge 1 commit into
anomalyco:devfrom
senguangd:fix/powershell-encodedcommand-utf8
Open

fix(shell): add PowerShell UTF-8 command wrapper on Windows#31985
senguangd wants to merge 1 commit into
anomalyco:devfrom
senguangd:fix/powershell-encodedcommand-utf8

Conversation

@senguangd

@senguangd senguangd commented Jun 12, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #23636
Closes #31187
Closes #30205
Closes #31830
Closes #26882

Supersedes #31925 (which was closed before the third code path was added)

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

On Windows, opencode captures shell output as bytes and decodes it as UTF-8. PowerShell, however, can write stdout/stderr using the active console code page when [Console]::OutputEncoding is not set explicitly. On non-UTF-8 systems, such as GBK/CP936 or Shift-JIS/CP932 environments, this can cause PowerShell output to be decoded incorrectly by opencode.

This PR adds a shared PowerShell command wrapper that:

  • Sets [Console]::InputEncoding to UTF-8
  • Sets [Console]::OutputEncoding to UTF-8
  • Sets `` to the same UTF-8 encoding
  • Runs the original user command as a separate scriptblock

The user command is stored inside the wrapper as an inner UTF-8 Base64 payload, then decoded and passed to [scriptblock]::Create(). The Base64 payload is used to avoid quoting and escaping issues when embedding arbitrary user command text inside the wrapper. The separate scriptblock invocation avoids prepending the UTF-8 setup directly to the user script, preserving script-level constructs such as leading param(...) blocks and #requires directives.

Files changed (all three PowerShell execution paths):

File Change
packages/core/src/shell/powershell.ts Add shared PowerShell argument/wrapper construction
packages/core/src/tool/bash.ts Detect PowerShell on Windows and invoke it through the shared wrapper
packages/core/src/shell.ts Use the shared PowerShell wrapper from Shell.args(...)
packages/opencode/src/tool/shell.ts Delegate PowerShell invocation to Shell.args(...) instead of hardcoding argv

How did you verify your code works?

  • Applied the patch locally and verified that Write-Output "测试中文输出" renders correctly in opencode shell output on Windows with a non-UTF-8 code page
  • Verified the same commands work with both powershell 5.1 and pwsh 7+
  • Tested TUI ! shell mode (! echo 你好世界) — Chinese output displays correctly
  • TypeScript typecheck passes (tsgo --noEmit on all 23 packages)

Screenshots / recordings

N/A — this is a shell encoding fix, not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Copilot AI review requested due to automatic review settings June 12, 2026 02:59

Copilot AI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates Windows PowerShell execution to use -EncodedCommand and a UTF-8 console-encoding prelude, and centralizes PowerShell argument construction in shared helpers to improve reliability of command execution on Windows.

Changes:

  • Route Windows PowerShell invocation through Shell.args(...) instead of hardcoded flags.
  • Add -EncodedCommand generation for PowerShell, including setting console input/output encoding to UTF-8.
  • Update the core bash tool to detect PowerShell shells and spawn them with encoded commands.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/opencode/src/tool/shell.ts Switches Windows PowerShell spawn args to the shared Shell.args(...) helper.
packages/opencode/src/shell/shell.ts Introduces Base64 UTF-16LE -EncodedCommand generation and updates PowerShell args accordingly.
packages/core/src/tool/bash.ts Adds PowerShell detection and uses -EncodedCommand spawning on Windows.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/opencode/src/shell/shell.ts Outdated
Comment on lines +159 to +169
function encodedPowerShellCommand(command: string) {
return Buffer.from(
`
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false);
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false);
$OutputEncoding = [Console]::OutputEncoding;
${command}
`,
"utf16le",
).toString("base64")
}

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.

Good catch — this was a real issue. I updated the implementation so the UTF-8 setup is no longer prepended directly to the user-provided PowerShell script text.

The encoded command is now a small wrapper that sets the console encodings first, then decodes the original user command and invokes it as a separate scriptblock. That preserves script-level constructs like leading param(...) blocks and #requires directives, while still keeping the UTF-8 behavior.

Comment thread packages/core/src/tool/bash.ts Outdated
Comment on lines +53 to +70
const POWERSHELL_SHELLS = new Set(["powershell", "powershell.exe", "pwsh", "pwsh.exe"])

const isPowerShell = (shell: string) => {
const name = path.basename(shell.trim().replace(/^["']|["']$/g, "")).toLowerCase()
return POWERSHELL_SHELLS.has(name)
}

function encodedPowerShellCommand(command: string) {
return Buffer.from(
`
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false);
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false);
$OutputEncoding = [Console]::OutputEncoding;
${command}
`,
"utf16le",
).toString("base64")
}

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.

Agreed. I extracted the PowerShell argument/encoding construction into a shared helper under @opencode-ai/core, and updated both execution paths to use it.

packages/opencode/src/shell/shell.ts now uses the shared PowerShell helper from Shell.args(...), and packages/opencode/src/tool/shell.ts reuses Shell.args(...) instead of building its own PowerShell argv. The core BashTool also uses the same shared helper, so the -EncodedCommand wrapper behavior stays consistent across the direct shell, shell tool, and core BashTool paths.

@Thalynor

Copy link
Copy Markdown

You mentioned that using the -Command corrupts the content. Could you tell me how to reproduce this issue?

@senguangd senguangd force-pushed the fix/powershell-encodedcommand-utf8 branch 2 times, most recently from 96a0fd0 to 18c5177 Compare June 16, 2026 10:07
@senguangd senguangd changed the title fix(shell): use PowerShell EncodedCommand for reliable UTF-8 output on Windows fix(shell): add PowerShell UTF-8 command wrapper on Windows Jun 16, 2026
@senguangd

Copy link
Copy Markdown
Author

@Thalynor Good question — I rechecked this and updated the PR to make the scope more precise.

The latest version no longer uses -EncodedCommand. A direct pwsh -Command <string> invocation is not the reliable reproduction point here: direct process creation on Windows passes the command line as Unicode, so simply changing the active code page does not necessarily corrupt the command text itself.

The actual issue this PR is addressing is the output decoding path: opencode captures child process output as bytes and decodes it as UTF-8, while PowerShell may write stdout/stderr using the active console code page unless [Console]::OutputEncoding is set explicitly. On non-UTF-8 systems, those bytes can be decoded incorrectly by opencode.

The fix now does this instead:

  • invokes PowerShell with -Command
  • sets [Console]::InputEncoding, [Console]::OutputEncoding, and $OutputEncoding to UTF-8 before running the user command
  • passes the original user command as an inner UTF-8 Base64 payload and invokes it via [scriptblock]::Create()

The inner Base64 is not meant to justify -EncodedCommand; it is just a safe way to embed arbitrary user command text in the wrapper without PowerShell quoting/escaping issues. The separate scriptblock invocation avoids prepending the UTF-8 setup directly to the user script, preserving leading param(...) blocks and #requires directives.

So the PR description has been updated to describe this as a PowerShell UTF-8 I/O wrapper fix rather than a -EncodedCommand transport fix.

@senguangd senguangd force-pushed the fix/powershell-encodedcommand-utf8 branch from 18c5177 to f4eded7 Compare June 16, 2026 10:19
On Windows, PowerShell commands need proper UTF-8 encoding setup to avoid corruption when the console code page is not UTF-8 (e.g. GBK/CP936 on zh-CN systems). This adds a shared PowerShell module that:

  - Sets [Console]::InputEncoding and OutputEncoding to UTF-8
  - Uses inner Base64 + [scriptblock]::Create() to preserve user command semantics (param(), #requires must be at script start)

Used by bash tool, shell tool, and TUI direct shell mode.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment