Windows Git Bash: multiple recurring command issues (nul, python, paths, flags, eval escaping)
Claude Code on Windows: Git Bash / MSYS2 command issues
Summary
Claude Code on Windows uses Git Bash (MSYS2) as its shell. Several recurring issues cause commands to fail silently, create undeletable files, or enter retry loops. These are predictable patterns that could be addressed with platform-aware defaults or built-in fixups.
Environment
- Windows 11 Pro
- Claude Code CLI (latest)
- Shell: Git Bash (GNU bash 5.2.37, x86_64-pc-msys)
- Python 3.13 (standard Windows installer)
- PowerShell 7.x (
pwsh)
Issues
1. > nul creates undeletable files
Claude frequently uses > nul to suppress output. In CMD this redirects to the null device, but in Git Bash nul is a regular filename. This creates a file literally named nul, which Windows Explorer and most tools cannot delete (it's a reserved device name at the filesystem API level).
# What Claude writes:
ping -n 1 192.168.1.1 > nul 2>&1
# What it should write:
ping -n 1 192.168.1.1 > /dev/null 2>&1
Impact: Leaves undeletable files in the working directory. Requires cmd /c "del \\?\C:\path\nul" or WSL to remove.
This was reported in #4928, but the problem extends well beyond nul -- see the remaining issues below for the full scope of Windows/Git Bash incompatibilities.
2. python3 doesn't exist on standard Windows Python installs
Claude uses python3 as the default Python command. When Python is installed via the standard MSI installer (python.org), only python and python.exe are available -- there is no python3 command. The python3 stub that Windows ships is a Microsoft Store redirect that opens the Store app instead of running Python. Users who installed Python normally (not from the Store) get a confusing Store prompt or a "command not found" error.
# What Claude writes:
python3 script.py
# What it should write:
python script.py
3. \$ escaping broken under eval
Claude Code executes bash commands through eval, which causes double-processing of escape sequences. \$_ in double quotes (intended to produce a literal $_) gets expanded in two passes instead of one:
- First pass (eval's parse):
\$is consumed as an escape, producing literal$ - Second pass (eval's execution): the now-unescaped
$_is expanded as a variable
$_ is a bash special variable holding the last argument of the previous command. Whatever that value happens to be gets substituted. In practice, Claude Code often runs shopt -s extglob internally, so $_ contains "extglob":
# What Claude writes:
pwsh -Command "Get-AppxPackage | Where-Object { \$_.Name -like '*Claude*' }"
# What bash eval produces (two passes):
# pass 1: \$_ -> $_ (escape consumed)
# pass 2: $_ -> "extglob" (variable expanded)
pwsh -Command "Get-AppxPackage | Where-Object { \extglob.Name -like '*Claude*' }"
This affects ANY \$ in double-quoted strings, not just PowerShell. The \$ -> literal $ escape documented in the bash manual does not survive eval's double-processing.
We verified this is reproducible and not context-dependent:
$ echo "\$_" # in direct shell: works, prints $_
$ bash -c 'echo "\$_"' # via eval: prints \extglob (or whatever $_ holds)
Workaround: Use single quotes for commands containing $ (no expansion occurs in single quotes):
pwsh -Command 'Get-AppxPackage | Where-Object { $_.Name -like "*Claude*" }'
When the command requires both $ and embedded single quotes, write to a .ps1 file and use pwsh -File:
cat > script.ps1 << 'EOF'
Get-AppxPackage -Name '*Claude*' |
Get-AppxPackageManifest |
Select-Xml -XPath '//*[local-name()="Protocol"]' |
ForEach-Object { $_.Node.OuterXml }
EOF
pwsh -File script.ps1
rm script.ps1
Note: This is not a PowerShell-specific issue. The eval double-processing affects any bash command that needs a literal $ character in double quotes. Claude should avoid \$ entirely and use single quotes or heredocs instead.
4. powershell.exe instead of pwsh
Claude defaults to powershell.exe (Windows PowerShell 5.1, legacy) instead of pwsh (PowerShell 7+). PS 5.1 is missing modern cmdlets, has different default encoding, and is in maintenance mode.
5. Backslash paths in bash
Claude generates Windows-style backslash paths (C:\Users\me\file.txt) in bash commands. These don't work reliably -- bash interprets \U, \m, etc. as escape sequences in some contexts. Forward slashes (C:/Users/me/file.txt) work everywhere.
# Fails or behaves unexpectedly:
cat "C:\Users\me\file.txt"
# Works:
cat "C:/Users/me/file.txt"
6. MSYS2 drive mount paths
Claude sometimes generates /c/Users/... (MSYS2 mount-style) paths. While these work with MSYS2 tools (ls, cat), they fail when passed to Windows executables (python.exe, reg.exe, etc.) because the path conversion doesn't always happen.
# May fail with Windows executables:
python /c/Users/me/script.py
# Always works:
python C:/Users/me/script.py
7. // flag doubling for Windows commands
Claude sometimes doubles the / in Windows command flags (e.g., tasklist //fi instead of tasklist /fi), presumably to prevent MSYS2 path conversion. Testing shows this is unnecessary for short flags and actually breaks the commands:
# FAILS:
tasklist //fi "imagename eq powershell.exe"
ipconfig //all
# Works:
tasklist /fi "imagename eq powershell.exe"
ipconfig /all
MSYS2 only converts arguments that look like Unix paths (/c/Users/...). Short flags like /fi, /all are NOT converted.
8. cmd /c CWD not inherited
When Claude uses cmd /c to run batch files, cmd.exe does not inherit Git Bash's working directory. This leads to "not recognized" errors and retry loops:
$ cd C:/Work/project && cmd /c build.bat
'build.bat' is not recognized as an internal or external command
$ ls build.bat
-rw-r--r-- 1 user 197121 625 Feb 26 14:33 build.bat # file exists!
Claude then retries the same command multiple times before giving up. The fix is to use the full Windows path: cmd /c "C:/Work/project/build.bat".
9. Reserved device names
Claude occasionally generates commands that redirect to or create files with Windows reserved device names (CON, PRN, AUX, COM1-9, LPT1-9). In Git Bash, these create files that are extremely difficult to delete.
# Creates an undeletable file:
touch con.txt
Suggested improvements
- Platform-aware defaults: When running on Windows with Git Bash, prefer
/dev/nullovernul,pythonoverpython3,pwshoverpowershell.exe, and forward slashes in paths.
- Avoid
\$in double quotes: The eval execution model breaks\$escaping. Use single-quoted strings for shell commands that contain$literals, especially for PowerShell$_pipeline variables.
- Single
/for Windows flags: Do not double-slash flags for Windows commands. MSYS2 does not convert short flags.
- Full paths with
cmd /c: When cmd.exe is needed (e.g., for .bat files), use the full Windows path to the script since cmd.exe doesn't inherit the bash CWD.
- Reserved name awareness: Never redirect to or create files named
nul,con,prn,aux,com1-com9,lpt1-lpt9on Windows.
Community workaround
We've built a set of PreToolUse hooks that intercept Bash commands and file writes before execution, automatically fixing or blocking these patterns:
- Auto-fixes:
> nulto> /dev/null,python3topython, pwsh quote swapping, MSYS2 path conversion,dir /btols -1,starttoos.startfile() - Blocks with suggestions: backslash paths, doubled
//flags,powershell.exe, WSL paths, reserved device names
The hooks are plain Python scripts, easy to install (python install.py), and configurable per-check via config.json.
This issue has 8 comments on GitHub. Read the full discussion on GitHub ↗