Repository navigation
Description
- I have searched for existing issues and confirmed this is not a duplicate.
What
Make the Bash tool's bash.txt prompt description shell-aware on Windows, so the LLM generates correct commands for whichever shell shell.ts actually selects.
Why
Today bash.txt is a static, Unix-only tool description. On Windows, shell.ts auto-selects either Git Bash (most users — anyone with Git for Windows) or cmd.exe (Anaconda Prompt / no Git in PATH). Both shells have incompatible syntax with the Unix examples in bash.txt, and the LLM has no guidance to adjust:
| Shell | What breaks | Example |
|---|---|---|
| Git Bash | Backslash paths silently destroyed (\U, \v = escape sequences) |
ls C:\Users\name → ls C:Usersname |
| cmd.exe | Multiline silently truncated, single quotes literal, $VAR doesn't expand |
echo "line1\nline2" → only "line1" |
These aren't bugs in the shells — they're expected behavior. The issue is that the LLM doesn't know which shell it's talking to.
Proposed solution
Generate bash.txt at runtime based on the detected shell. Small additions per shell:
- Git Bash → "Use forward slashes for all paths. Never use backslashes."
- cmd.exe → "Double quotes only (no single quotes). No
$VAR(use%VAR%). Never pass multiline strings inline — write to temp file." - PowerShell → "Use PowerShell syntax,
$env:VARfor env variables."
This is a minimal change in how the tool description is loaded — no architectural changes needed.
Complementary (optional)
- Path normalization in
bash.ts— convertC:\...toC:/...beforespawn()as a safety net for Git Bash users. - Shell selection in config — let users set preferred shell in
opencode.jsoncinstead of auto-detection.
Evidence
Tested on two Windows machines:
- Machine A (Git for Windows installed) →
shell.tsselects Git Bash → backslash paths corrupted - Machine B (Anaconda Prompt, Git not in PATH) →
shell.tsselects cmd.exe → multiline/quoting issues
Full test matrix (25+ scenarios across both shells)
Git Bash (bash.exe) — default shell for most Windows users
| Scenario | Quoting | Result |
|---|---|---|
echo C:\Users\vzssyd |
unquoted | ❌ C:Usersvzssyd (backslashes eaten) |
echo "C:\Users\vzssyd" |
double-quoted | ✅ preserved |
echo 'C:\Users\vzssyd' |
single-quoted | ✅ preserved |
echo $'C:\Users\vzssyd' |
dollar-quoted | ❌ \v → vertical tab |
ls C:\Users\vzssyd\Desktop |
unquoted | ❌ No such file |
ls "C:\Users\vzssyd\Desktop" |
double-quoted | ✅ works |
mkdir C:\Users\vzssyd\test |
unquoted | ❌ creates C:Usersvzssydtest in cwd |
cp C:\a\b C:\c\d |
unquoted | ❌ fails |
myvar=C:\path |
unquoted assign | ❌ backslashes lost |
sed 's/old/C:\Users\new/' |
sed pattern | ❌ \U→uppercase, \n→newline |
node -e "..." C:\path |
arg to exe | ❌ lost (bash parses first) |
for f in C:\a C:\b |
for loop | ❌ lost |
heredoc <<EOF |
heredoc body | ✅ preserved |
workdir parameter |
N/A (Node cwd) |
✅ works (bypasses bash) |
cmd.exe — only when Git Bash not in PATH
| Scenario | Command | Expected | Actual | Severity |
|---|---|---|---|---|
| Multiline string | echo "line1\nline2\nline3" |
3 lines | Only "line1" — rest silently truncated |
🔴 Data loss |
| Single quotes | echo 'hello' |
hello |
'hello' (quotes treated as literal chars) |
🟡 Unexpected |
| jq single quotes | gh --jq '.[0].title' |
Parsed | unexpected token "'" — hard crash |
🔴 Crash |
$VAR expansion |
echo $PATH |
PATH value | Literal $PATH (cmd uses %VAR%) |
🟡 Silent fail |
| Same-line set+echo | set "X=hi" && echo %X% |
hi |
%X% (expanded at parse time, before set runs) |
🟡 Silent fail |
| PowerShell here-string | powershell -Command "@'...'@" |
Multiline | TerminatorExpectedAtEndOfString |
🔴 Crash |
| jq double quotes | gh --jq ".[0].title" |
Parsed | ✅ works | ✅ |
%VAR% expansion |
echo %USERNAME% |
Username | ✅ works | ✅ |
&& chaining |
echo a && echo b |
Two lines | ✅ works | ✅ |
| Pipe | echo hello | findstr "hello" |
hello |
✅ works | ✅ |
| Unicode/emoji | echo "✅ 🟢 中文" |
As-is | ✅ works | ✅ |
| Node.js | node -e "console.log('中文')" |
中文 |
✅ works | ✅ |
| Paths with spaces | type "C:\path with spaces\f.txt" |
Content | ✅ works (when quoted) | ✅ |
| for loop | for %i in (1 2 3) do echo %i |
1 2 3 | ✅ works | ✅ |
Root cause in source code
Shell selection: shell.ts
shell.ts → fallback() scans PATH at spawn time for bash.exe. The result depends entirely on what's installed and where it appears in PATH — not which Terminal app launched OpenCode.
| Environment | Shell selected | Why |
|---|---|---|
| Git for Windows installed (most users) | Git Bash (C:\Program Files\Git\usr\bin\bash.exe) |
bash.exe found first in PATH |
| Anaconda Prompt / no Git in PATH | cmd.exe | No bash.exe found, falls back to %COMSPEC% |
Command execution: bash.ts line 172
const proc = spawn(params.command, {
shell, // ← Git Bash (bash.exe) or cmd.exe
// ...
})Data flow: LLM JSON {"command": "ls C:\\\\Users\\\\vzssyd"} → JSON parse → string ls C:\Users\vzssyd → spawn(command, {shell}) → shell-specific interpretation → corruption.
Tool description: bash.txt
All path examples are Unix-style (/Users/name/...). No mention of Windows backslash hazard or cmd.exe quoting/multiline limitations. The LLM has no guidance for either shell variant.
Workaround
We've been running a prompt-level workaround that injects shell-aware rules into the agent's instruction files (AGENTS.md). The LLM checks env.Platform at session start and applies the matching rules. It's been working well in daily use on both machines.
Full workaround setup
Add this to your AGENTS.md (or CLAUDE.md / system prompt):
Step 1: Platform check header
At the top of your instruction file, add a platform gate so the LLM knows to check before running any command:
> ⚠️ **PLATFORM CHECK** — If `env.Platform` is `win32`, check the Shell table below
> for which shell this host uses. cmd.exe needs workarounds; Git Bash works like normal Unix.Step 2: Shell identification table
Create a table mapping each Windows machine to its actual shell (you can detect this by running echo $SHELL — if it returns a path, it's Git Bash; if it echoes $SHELL literally, it's cmd.exe):
| Shell | Hosts | Key Constraint |
|-------|-------|----------------|
| zsh | Mac | Standard Unix, no issues |
| Git Bash | (your Git Bash machines) | Forward slashes, quote spaces |
| cmd.exe | (your cmd.exe machines) | No multiline, no `'`, no `$VAR` |Step 3: Shell-specific rules
## Shell Execution Rules
The Bash tool spawns different shells per platform.
**Check `env.Platform` at session start** to apply the correct rules.
### macOS — zsh (no special rules)
Standard Unix shell. Single/double quotes, `$VAR`, multiline strings, pipes — all work as expected.
### Windows — check Shell table first
OpenCode's shell depends on what's in PATH at spawn time, **not** the Terminal app.
Most Windows machines with Git for Windows get **Git Bash**;
special setups (e.g. Anaconda Prompt) get **cmd.exe**.
#### Git Bash (default for most Windows hosts)
Works like normal Unix (zsh/bash). Minor differences:
1. **Use forward slashes in paths** — `C:/Users/Alex`, not `C:\Users\Alex`
(backslashes get interpreted as escape sequences).
The `workdir` parameter also requires forward slashes.
2. **Quote paths with spaces** using double quotes — `"C:/Program Files/foo"`.
3. **Beware MSYS2 auto path conversion** — Git Bash silently converts `/c/...` args to `C:\...`.
This breaks tools like Docker that take Unix-style paths as arguments.
→ Prefix with `MSYS_NO_PATHCONV=1` to disable: `MSYS_NO_PATHCONV=1 docker run -v /c/Users/...`
4. **Prefer built-in tools over Bash for file operations.**
Read/Write/Edit/Glob/Grep tools bypass the shell entirely — safest option.
Only use Bash for actual terminal commands (git, npm, gh, docker, etc.).
#### cmd.exe (special setups only)
1. **Never send multiline commands inline.**
cmd.exe silently drops everything after the first `\n`.
→ Write content to a temp file, then reference it
(e.g. `gh issue edit --body-file %TEMP%\body.md`).
→ Chain sequential commands with `&&` on a single line.
2. **Never use single quotes `'` — always use double quotes `"`.**
cmd.exe treats `'` as a literal character, not a string delimiter.
→ `gh --jq '.name'` ✗ → `gh --jq ".name"` ✓
3. **Never use `$VAR` — use `%VAR%` or avoid shell variables entirely.**
→ `echo %USERNAME%` ✓ | `echo $USER` ✗
4. **Never use PowerShell-specific syntax inline.**
`@"..."@`, `$(...)`, `-not`, `Where-Object` — none work in cmd.
→ `powershell -Command "simple single-line"` or write a `.ps1` file.
5. **Escape special characters in strings** — `>`, `<`, `|`, `&` are shell metacharacters in cmd.
→ Use `^` to escape: `echo foo ^& bar`, `echo foo ^| bar`.
6. **`%` in for loops doubles inside `.cmd` scripts** — `for %i` works interactively, but `.cmd` files need `%%i`.
7. **Prefer built-in tools over Bash for file operations.**
Read/Write/Edit/Glob/Grep tools bypass the shell entirely — no cmd.exe corruption risk.How it works
OpenCode already injects env.Platform into the context at session start. The LLM reads it, matches against the rules above, and generates shell-appropriate commands. On macOS sessions, the Windows rules are completely ignored — zero overhead.
Results
After deploying these rules, shell-related failures dropped to near zero in daily usage across both Windows machines. The only remaining edge case is very long conversations where context pressure causes the LLM to "forget" the rules, but that's rare.
This is a prompt-level workaround, not a fix. The real solution is a dynamic bash.txt that adapts to the detected shell automatically — which is what this feature request is about.
Relevant source
packages/opencode/src/tool/bash.txt— static Unix-only tool descriptionpackages/opencode/src/tool/bash.tsline 172 —spawn(command, {shell})with no path normalizationpackages/opencode/src/shell/shell.ts—fallback()auto-selects bash.exe or cmd.exe
Environment
- OpenCode 1.2.16
- Windows 11 Enterprise, two machines (Git Bash default + cmd.exe default)
Activity
github-actions commentedon Mar 7, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
[FEATURE]:for feature requests (currently starts withFeature:)Please edit this issue to address the above within 2 hours, or it will be automatically closed.
This issue might be a duplicate of existing issues. Please check:
exportcommand in environment variable setup — same root cause (Windows shells incompatible with Unix-style bash tool behavior), though that issue is now closedIf you believe this was flagged incorrectly, please let a maintainer know.
[-]Feature: shell-aware Bash tool prompt for Windows (Git Bash / cmd.exe / PowerShell)[/-][+][FEATURE]: Shell-aware Bash tool prompt for Windows (Git Bash / cmd.exe / PowerShell)[/+]github-actions commentedon Mar 7, 2026
Thanks for updating the issue! It now meets the contributing guidelines and looks great. The description clearly explains the problem, includes evidence from real testing, and provides a concrete proposed solution. The needs:compliance label has been removed.
github-actions commentedon Mar 7, 2026
Thanks for updating the issue! It now follows the contributing guidelines and looks great. The needs:compliance label has been removed.
lazyalex616 commentedon Mar 30, 2026
This has been addressed by #16069 (merged in v1.3.7), which adds first-class PowerShell support on Windows — including dynamic shell-aware prompt templates (\, \, \), \ ree-sitter-powershell\ command parsing, and dedicated PowerShell spawn logic. The core ask of this issue (make bash.txt adapt to the detected shell) is fully covered. Closing.