[BUG] Windows: the PowerShell tool targets 5.1, so models migrate to Bash and users end up maintaining the workarounds
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
[BUG] Windows: the PowerShell tool targets 5.1, so models migrate to Bash and users end up maintaining the workarounds
Summary
On Windows the PowerShell tool executes through Windows PowerShell 5.1 — the
in-box version, feature-frozen since 2016. Models, trained largely on POSIX and
PowerShell 7 idioms, emit syntax it cannot run. The current mitigation is a block
of caveats in the tool description, which is probabilistic and fails open.
The measurable cost is not the failure count. It is the adaptation: sessions
have migrated ~80% of all shell work to the Bash tool to avoid PowerShell, and six
unrelated projects on this machine have accumulated 35 memory files whose only
purpose is to work around shell behaviour — including four separate projects that
each independently derived "never chain shell commands" under four different
names.
That is the signature of a defect in the substrate: the same lesson re-learned from
scratch, project after project, because there is nowhere else for it to live.
Related but distinct: #83889 asks for the same pwsh preference for the Desktop
integrated terminal; this issue is about the PowerShell tool the model runs
commands through. #7134 covers Edit/Write forcing UTF-8 onto non-UTF-8 files; the
encoding case below is a different pathway (shell cmdlets, not the file tools).
Environment
- Windows 11 Pro, build 26100 (24H2); machine built 2024
powershell.exe→ 5.1.26100.2161;$PSHOME=C:\Windows\System32\WindowsPowerShell\v1.0;
CLR 4.0.30319; exe ProductVersion 10.0.26100.1 (versioned as Windows — an OS component)
pwsh(PowerShell 7) not installed. Nothing has ever prompted for it, and
nothing in Claude Code requires it. On a stock Windows box, 5.1 is "PowerShell".
- Git Bash present; both a PowerShell tool and a Bash tool are exposed
Evidence
Method: parsed every session transcript under ~/.claude/projects/**/*.jsonl,
pairing each tool_use for the PowerShell/Bash tools with its tool_result.
Period 2026-07-15 → 2026-08-04: 106 transcripts, 6 projects, ~5,828 shell calls.
1. Models are abandoning the PowerShell tool
2026-07: Bash 3104 (76%) PowerShell 962 (24%)
2026-08: Bash 1439 (82%) PowerShell 323 (18%)
Driven by explicit instructions, in sessions and in project docs, to prefer Bash
"because PowerShell keeps failing". Any low PowerShell failure rate measured
today is a product of that migration, not evidence the tool works.
2. Rework
| Measure | Count |
|---|---|
| Shell calls that errored (any cause) | 308 (5.3%) |
| Commands reissued in changed form after a failure | 190 |
| Shell-syntax / quoting errors | 18 |
| Wrong-shell syntax that failed | 32 |
| Wrong-shell syntax that ran anyway (latent) | 50 |
| git commit calls / errored / redone | 376 / 11 / 5 |
3. Workaround memories: 53 files across 6 projects, 35 shell/tooling
Independently derived, in separate projects, for the same underlying causes:
one-command-per-tool-call(×2),avoid-compound-shell-commands,
no-chained-shell-commands, avoid-chained-shell-commands
powershell-quote-mangling(×2),commit-messages-via-file,full-paths-in-commandspermissions-setup,credits-ocr-permissions-layout,cc-permission-rules-windowsuser-shell-is-cmd,no-pipe-app-through-head-grep,windows-cmd-and-recoverable-deletes
Failure classes
- PowerShell version mismatch —
&&,||, ternary,??,?.,
-AsHashtable, sane 2>&1, UTF-8 defaults. Valid in 7, fail in 5.1.
Preferring pwsh eliminates this class entirely.
- Unix idioms —
head,tail,grep,which,touch,2>/dev/null,
VAR=x cmd. Exist in neither PowerShell. pwsh does not help here;
routing to the Bash tool would.
- Quoting / here-strings — the largest observed cause of hard failures,
dominated by git commit -m @'...'@ where the message contains double quotes.
Reproducible examples
A. A Get-Content/Set-Content round-trip silently destroys a UTF-8 source file
$t = Get-Content file.py -Raw # decodes as the ANSI codepage
$t = $t.Replace('a','b')
Set-Content file.py -Value $t -Encoding utf8 # writes UTF-8 *with BOM*
Every non-ASCII character (—, §, →) becomes mojibake and a BOM is prepended.python -m py_compile still passes. In my case this corrupted an 11,400-line
tracked source file during three string replacements; the only signal was a
1,642-line git diff on a file touched in three places. It is recoverable
(reverse the cp1252 round trip), but nothing in the tooling flags it, and a less
careful review would have committed it.
B. Captured output is decoded with the wrong encoding — ~142 occurrences
This is not a one-off. Scanning the same 106 transcripts for mojibake signatures
turns up ~142 occurrences of text corrupted in capture, across 5 projects, in
two distinct forms:
| Signature | Count | Example (all from the tools' own console messages) |
|---|---|---|
| Replacement char (U+FFFD) — em-dash destroyed | 123 | IMDb Ingest v027 <?> Self-Test |
| UTF-8 decoded as cp1252 | 21 | multiple strong persistent regions — check candidate boxes |
Distribution: 112 InpaintDelogo, 28 Make VOBs, 7 Credits OCR, 2 Cast Generator,
1 IMDb Ingest.
The capture layer is the variable, not the emitting program. The same
warning line from the same tool on the same day appears both ways in
different tool calls:
WARNING: multiple strong persistent regions — check candidate boxes ...
WARNING: multiple strong persistent regions <?> check candidate boxes ...
Identical source bytes, two different manglings. At least one occurrence is a
file read rather than subprocess output (The spike is milestone one <?> from a markdown file), so this is not limited to
nothing else gets built first
process stdout.
A previous session's workaround, found in the transcripts, shows the cost
landing on users rather than being reported:
assert t.count('�') > 50, 'em-dashes lost'
Deliberately excluded from that count: mojibake belonging to my own software's
domain — a subtitle OCR path where Subtitle Edit decodes Tesseract output via the
console codepage. That class appears 8 times in the transcripts and every one is
a session reading my documentation about it, not a new incident.
C. Here-string commit messages break on double quotes
git commit -m @'
fix: the notice now reads "1 row" not "1 rows"
'@
# -> error: pathspec 'row' did not match any file(s) known to git
Requested changes
- Prefer
pwshwhen installed for the PowerShell tool, falling back to
powershell.exe. Eliminates failure class 1. (#83889 asks for the same on the
integrated terminal — one discovery path could serve both.)
- Surface PowerShell 7's existence on Windows — first-run hint or setup docs.
Nothing today tells users 5.1 is a frozen 2016 feature set.
- **Decode captured output as UTF-8, and force UTF-8 in the 5.1 session
bootstrap** — [Console]::OutputEncoding, $OutputEncoding,
$PSDefaultParameterValues['*:Encoding']. Example B is the highest-volume
defect here (~142 occurrences in three weeks) and it silently degrades every
piece of tool output the model reads. (5.1's Set-Content -Encoding utf8
always writes a BOM; only pwsh or [IO.File]::WriteAllText fully fixes
that half.)
- Route POSIX-shaped commands to the Bash tool when it is available, rather
than failing in PowerShell — fail closed instead of open.
- Guardrail against shell-based rewrites of tracked source files, since
example A passes every syntax check and surfaces only in a diff.
What this report does not claim
- Hard syntax failures are a small share of errors (18 of 308). Most errors were
ordinary: test suites exiting 1, greps matching nothing, missing files. The
argument rests on adaptation cost, not error count.
- Numbers are ±a few calls across parser revisions. An earlier count of mine was
3× too high before correction (a regex matching ?? counted git's untracked-file
markers as PowerShell null-coalescing). Figures above are post-correction and
based on command shape rather than output text.
What Should Happen?
- The PowerShell tool should launch
pwsh(PowerShell 7) when it is installed,
falling back to powershell.exe only when it is not. Today it always uses
Windows PowerShell 5.1, whose feature set has been frozen since 2016.
- Reading and writing files through the PowerShell tool should preserve the
file's encoding. Today the idiomatic 5.1 form (Get-Content -Raw /
Set-Content -Encoding utf8) decodes as the ANSI codepage and writes back
UTF-8 with a BOM, corrupting every non-ASCII character in a tracked source
file, with no error at any stage and no failed syntax check afterwards.
- A command written in POSIX form should be routed to the Bash tool where one is
available, rather than executed in PowerShell where it cannot parse. The
failure should be closed (routed or refused), not open (executed and mangled).
- A multi-line string — a commit message in particular — should survive being
passed to a command. Today a PowerShell here-string terminates at the first
double quote inside it, and the remainder of the message reaches git as
pathspecs.
- Windows setup should surface that PowerShell 7 exists. On a stock Windows 11
machine, 5.1 is presented simply as "PowerShell" and nothing indicates that a
current version is available separately.
- Working with a repository on Windows should not require the user to accumulate
per-project instruction files that encode shell workarounds. Six projects on
this machine hold 35 such files; four independently derived "never chain shell
commands" under four different names.
Error Messages/Logs
Samples are verbatim, from session transcripts under
~/.claude/projects/**/*.jsonl (106 sessions, 2026-07-15 to 2026-08-04) and from
a controlled reproduction run today.
1. SILENT ENCODING CORRUPTION (reproduced on demand; see Steps to Reproduce).
A Get-Content/Set-Content round trip over a clean UTF-8 file:
before: alpha — beta § gamma → delta
bytes 616c706861 20 e28094 20 6265746120 c2a7 ... BOM: False
after: ALPHA â€" beta § gamma â†' delta
bytes efbbbf 414c504841 20 c3a2e282ace2809d ... BOM: True
PowerShell printed "done". No error, no warning.
2. The same defect against a real 11,400-line tracked source file, edited in
three places. It passed `python -m py_compile`. The only signal was the
diff size:
$ git diff --numstat
1642 1309 make_vobs.py
Manual recovery required reversing the round trip, which is itself lossy
for the five cp1252-undefined byte values:
'charmap' codec can't encode character '\x81' in position 52146
3. HERE-STRING QUOTING. A commit message containing double quotes terminates
the here-string early; the remaining words reach git as pathspecs:
error: pathspec 'rows' did not match any file(s) known to git
error: pathspec 'contain.' did not match any file(s) known to git
4. INLINE PYTHON through the PowerShell tool:
File "<string>", line 7
print(f'{" film\:44s}
^
SyntaxError: unterminated string literal (detected at line 7)
py.exe : ScriptBlock should only be specified as a value of the Command parameter.
5. NOTABLE ABSENCE: there are no `&&` parser errors in my logs. Sessions had
already been instructed to prefer the Bash tool, so PowerShell's limits stop
producing errors and start producing avoidance — see the 76% -> 82% Bash
shift in the issue body. Issue #83889 has a first-hand `&&` failure.
Steps to Reproduce
Environment: Windows 11 (any build), no PowerShell 7 installed, so the
PowerShell tool uses Windows PowerShell 5.1. Verify with:
$PSVersionTable.PSVersion -> 5.1.26100.2161
- Create a UTF-8 text or source file containing any non-ASCII character
(an em-dash is enough):
alpha — beta § gamma → delta
Confirm it is clean UTF-8 with no BOM:
bytes: 616c706861 20 e28094 20 6265746120 c2a7 ...
BOM: False
- Ask Claude Code to make a small textual edit to that file. On Windows it
may reach for the PowerShell tool and run the idiomatic form:
$t = Get-Content $p -Raw
$t = $t.Replace('alpha','ALPHA')
Set-Content $p -Value $t -Encoding utf8
- The command reports success. No error, no warning, and the file still
parses — python -m py_compile passes on a .py file.
- Inspect the bytes. Every non-ASCII character is corrupted and a BOM has
been prepended:
ALPHA â€" beta § gamma â†' delta
bytes: efbbbf 414c504841 20 c3a2e282ace2809d ...
BOM: True
Cause: 5.1's Get-Content decodes as the ANSI codepage rather than UTF-8,
and Set-Content -Encoding utf8 writes UTF-8 with a BOM.
- On a real file the only visible signal is diff size. In my case this hit
an 11,400-line tracked source file during three string replacements and
produced a 1,642-line diff, which is the only reason it was caught before
being committed.
Claude Model
Not sure / Multiple models
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
Claude 1.24012.11 (desktop app, MSIX: Claude_1.24012.11.0_x64__pzs8sxrjxfjjc) No standalone CLI installed — Claude Code is running inside the desktop app.
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗