Skip to content

feat(windows): add first-class pwsh/powershell support - #16069

Merged
Hona merged 39 commits into
anomalyco:devfrom
Hona:experimental/windows-shells
Mar 30, 2026
Merged

feat(windows): add first-class pwsh/powershell support#16069
Hona merged 39 commits into
anomalyco:devfrom
Hona:experimental/windows-shells

Conversation

@Hona

@HonaHona commented Mar 5, 2026

Copy link
Copy Markdown
Member

Summary

  • Add first-class pwsh / powershell support on Windows by preferring PowerShell over Git Bash when selecting the default shell
  • Parse PowerShell commands with tree-sitter-powershell so permission scanning covers cmdlets, env paths, FileSystem:: providers, and ${env:...} syntax
  • Resolve POSIX virtual paths (/tmp, /usr/...) through the selected shell's own cygpath when running under Git Bash/MSYS/Cygwin
  • Fix /usr/bin/bash from $SHELL resolving to a non-spawnable path on Windows by mapping it through Shell.gitbash()
  • Fix drive-root globs (C:\*) collapsing to the repo directory instead of the drive root in normalizePathPattern
  • Scan nested command substitutions ($(cat ...), $(Get-Content ...)) for external directory permissions
  • Hide spawned console windows on all Windows shell paths, not just PowerShell
  • Centralize shell name extraction (Shell.name), login detection (Shell.login), and POSIX detection (Shell.posix) in the Shell namespace
  • Replace Unix-only test fixtures (seq, /dev/zero) with cross-platform inline bun -e generators
  • Add focused Windows regression coverage across shell selection, external directory prompts, PowerShell command parsing, and Git Bash path semantics

Use pwsh and powershell before Git Bash when SHELL is unset on Windows so the default shell matches native expectations more closely. Surface the active OS and shell in the bash tool definition so agents can reason about the runtime they are executing in.
CopilotAI review requested due to automatic review settings March 5, 2026 00:04

CopilotAI 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

Adjusts Windows shell selection defaults to prefer PowerShell-family shells and improves runtime transparency for the bash tool by surfacing OS/shell context in its description.

Changes:

  • Update Shell.preferred() / Shell.acceptable() to prefer pwsh, then powershell, when SHELL is unset on Windows.
  • Keep existing Windows Git Bash path-resolution fallback behavior intact.
  • Inject process.platform and a resolved shell name into the bash tool description template.

Reviewed changes

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

FileDescription
packages/opencode/src/tool/bash.txtAdds OS + resolved shell placeholders to the tool description.
packages/opencode/src/tool/bash.tsResolves shell “display name” and substitutes OS/shell into the tool description.
packages/opencode/src/shell/shell.tsIntroduces base() helper and changes Windows default shell preference order.

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

Comment threadpackages/opencode/src/shell/shell.ts Outdated
Comment threadpackages/opencode/src/shell/shell.ts Outdated
Comment threadpackages/opencode/src/shell/shell.ts Outdated
Remove the one-off helper used for shell name normalization and keep the Windows blacklist check inline. This keeps the shell selection logic simpler without changing behavior.
@HonaHona changed the title fix(windows): prefer PowerShell defaults for shell toolsfeat(windows): prefer PowerShell defaults for shell toolsMar 5, 2026
Hona added 2 commits March 6, 2026 12:48
Rename the terminal tool around shell semantics instead of Bash while preserving backward compatibility for existing messages, permissions, and prompts. Automatically migrate file-based bash config to shell so Windows beta testing works without manual config edits.
@HonaHona changed the title feat(windows): prefer PowerShell defaults for shell toolsrefactor(shell): rename bash tool and harden Windows supportMar 6, 2026
@HonaHona changed the title refactor(shell): rename bash tool and harden Windows supportfix(windows): harden shell handling without Git Bash internalsMar 6, 2026
@HonaHona changed the title fix(windows): harden shell handling without Git Bash internalsfeat(windows): add first-class pwsh/powershell supportMar 6, 2026
Hona added 5 commits March 29, 2026 12:11
Use the same Git Bash lookup as production and inline the truncation generators so the tests stop depending on host-specific PATH entries and an extra fixture file.
Resolve Git Bash's /usr/bin/bash placeholder to a real Windows executable and teach PowerShell path scanning to unwrap FileSystem:: and braced env paths so external directory prompts stay accurate.
Normalize drive-root globs without resolving them into the repo and hide spawned PowerShell windows so bash tool approvals and execution stay correct on Windows.
Use the selected shell to translate unresolved POSIX-style paths on Windows so Git Bash keeps /tmp and /usr semantics, and make that shell check explicit with Shell.posix while restoring hidden console windows for non-PowerShell launches.
@Hona

Hona commented Mar 29, 2026

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

Copy link
Copy Markdown
Contributor

lgtm

Hona added 4 commits March 30, 2026 09:24
Include command substitutions in bash and PowerShell permission scanning so nested file access still triggers external directory approvals and precise bash patterns.
Shorten multi-word identifiers, use functional filter for commands(), and drop stray .ts import extension.
Avoid re-resolving Instance.directory through a different Windows realpath path when no workdir is provided, so the project root does not spuriously trigger external_directory permissions.
Keep drive-relative PowerShell paths in filesystem scanning and resolve common built-in path variables so external_directory prompts still fire for normal Windows path syntax.
Comment threadpackages/opencode/src/pty/index.ts
@caozhiyuan

Copy link
Copy Markdown
Contributor

@Hona How to configure using git bash? Previously, cross-team collaboration used git bash scripts, but now pwsh is completely incompatible, causing all agent automation tasks to fail.

@Hona

Hona commented Mar 30, 2026

Copy link
Copy Markdown
MemberAuthor

@caozhiyuan what automation? I would love to hear more about the cross team use case?

@Hona

Hona commented Mar 30, 2026

Copy link
Copy Markdown
MemberAuthor

but yeah set an env var if you want git bash again, e.g.

SHELL=C:\Program Files\Git\bin\bash.exe

@caozhiyuan

Copy link
Copy Markdown
Contributor

@Hona some bash script in slash command or skill .

@caozhiyuan

caozhiyuan commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

It is recommended to use gitbash by default. Previously, claude code on Windows had very slow file reading and bash operations due to cygpath not having caching (this issue lasted for several months without being fixed, resolved in version 2.1.39, and now it runs smoothly anthropics/claude-code#13361 ). Therefore, many people chose opencode (including me). Also, it would be best to add a switch to choose between gitbash and pwsh. Claude code now supports pwsh as well, but it needs to be manually enabled. @Hona

On Windows, I also added this tip: # Bash Tips
Currently on Windows system, bash tools run in Git Bash and paths need to be enclosed in double quotes, for example: ls -lc "c:\work"
Some open-source models encounter issues when using bash, so this tip was specifically added.

@CCRcmcpe

Copy link
Copy Markdown

constchain=
name==="powershell"
? "If the commands depend on each other and must run sequentially, avoid '&&' in this shell because Windows PowerShell 5.1 does not support it. Use PowerShell conditionals such as `cmd1; if ($?) { cmd2 }` when later commands must depend on earlier success."
: "If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead."

we should change the prompt if pwsh is selected.

@Hona

Hona commented Mar 31, 2026

Copy link
Copy Markdown
MemberAuthor

yup i'm further refactoring pwsh/powershell into separate tools

@caozhiyuan

Copy link
Copy Markdown
Contributor

@Hona It seems that the claude code source code has leaked, with cli.js.map uploaded to npm.
https://www.npmjs.com/package/@anthropic-ai/claude-code/v/2.1.88?activeTab=code

@nikitakot

Copy link
Copy Markdown

@Hona this PR introduced a very trivial bash permission bypass in powershell, all commands which contain -- bypass the permissions completely, I created an issue and a potential fix for that:

#39931
#39938

In the fix I propose to fallback to full command evaluation if web-tree-sitter parsing fails.

Here's an isolated example:

import{Parser,Language}from"web-tree-sitter"import{createRequire}from"node:module"constrequire=createRequire(import.meta.url)awaitParser.init({locateFile(){returnrequire.resolve("web-tree-sitter/tree-sitter.wasm")},})constinputs=["git diff","git diff --","git diff -- file","git status --","echo --"]for(constgrammarof["tree-sitter-powershell","tree-sitter-bash"]){constlanguage=awaitLanguage.load(require.resolve(`${grammar}/${grammar}.wasm`))constparser=newParser()parser.setLanguage(language)console.log(`\n## ${grammar}`)for(constinputofinputs){consttree=parser.parse(input)constcommands=tree.rootNode.descendantsOfType("command").map((node)=>node.text)console.log(JSON.stringify({ input,tree: tree.rootNode.toString(), commands,error: tree.rootNode.hasError}))}}

output

$ node .\parse.mjs## tree-sitter-powershell
{"input":"git diff","tree":"(program (statement_list (pipeline (pipeline_chain (command command_name: (command_name) command_elements: (command_elements (command_argument_sep) (generic_token)))))))","commands":["git diff"],"error":false}
{"input":"git diff --","tree":"(ERROR (command_name) (command_argument_sep) (generic_token) (command_argument_sep))","commands":[],"error":true}
{"input":"git diff -- file","tree":"(ERROR (command_name) (command_argument_sep) (generic_token) (command_argument_sep) (simple_name))","commands":[],"error":true}
{"input":"git status --","tree":"(ERROR (command_name) (command_argument_sep) (generic_token) (command_argument_sep))","commands":[],"error":true}
{"input":"echo --","tree":"(ERROR (command_name) (command_argument_sep))","commands":[],"error":true}
## tree-sitter-bash
{"input":"git diff","tree":"(program (command name: (command_name (word)) argument: (word)))","commands":["git diff"],"error":false}
{"input":"git diff --","tree":"(program (command name: (command_name (word)) argument: (word) argument: (word)))","commands":["git diff --"],"error":false}
{"input":"git diff -- file","tree":"(program (command name: (command_name (word)) argument: (word) argument: (word) argument: (word)))","commands":["git diff -- file"],"error":false}
{"input":"git status --","tree":"(program (command name: (command_name (word)) argument: (word) argument: (word)))","commands":["git status --"],"error":false}
{"input":"echo --","tree":"(program (command name: (command_name (word)) argument: (word)))","commands":["echo --"],"error":false}

It's already being fixed in tree-sitter-powershell in airbus-cert/tree-sitter-powershell#51, but I believe that fallback fix I'm proposing is the correct approach, allowing to run the bash command when parsing fails doesn't sound right at all.

@nikitakot

Copy link
Copy Markdown

@Hona this bug was also merged to V2 from what I can see

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@Hona@caozhiyuan@CCRcmcpe@nikitakot@morirain@rekram1-node@thdxr