[BUG] Windows + Git Bash: tengu_cobalt_ridge experiment silently force-enables the PowerShell tool & "Shell: PowerShell", causing wrong-shell retries (token waste)
Summary
On a Windows system whose active/launching shell is Git Bash, Claude Code silently enrolled this account into the tengu_cobalt_ridge experiment, which default-enables the native PowerShell tool and injects Shell: PowerShell (use PowerShell syntax …) into the system-prompt environment block. Nothing in the UI signals that a beta feature was turned on.
Because the surrounding toolchain on this machine (hooks, skills, statusline, allow-listed commands) is bash-based, telling the model "you are in PowerShell" produces systematic wrong-syntax tool calls → failures → retries → wasted tokens. This is the core defect: an experiment flipped the execution shell and tool schema out from under a correctly-bash environment, with no notice and no SHELL-awareness.
Version / environment
- Claude Code: 2.1.158 (native installer build)
- OS: Windows 11
- Git Bash: installed and detected —
CLAUDE_CODE_GIT_BASH_PATHset to a validbash.exe - Launch shell: Git Bash (process chain:
cmd → bash → bash → claude.exe) CLAUDE_CODE_USE_POWERSHELL_TOOL: unset (at time of bug)
What gets injected
The environment block advertised to the model:
Shell: PowerShell (use PowerShell syntax — e.g., $null not /dev/null, $env:VAR not $VAR, backtick for line continuation). Bash is also available via the Bash tool for POSIX scripts.
…even though process.env.SHELL resolves to a bash path and the process was launched from Git Bash. A dedicated PowerShell tool is also registered in the toolset.
Root cause (reconstructed from the shipped 2.1.158 binary)
The shell/tool gate consults the experiment as its fall-through default:
function qC(){
let H = process.env.CLAUDE_CODE_USE_POWERSHELL_TOOL;
if (platform() !== "windows") return isTruthy(H);
if (isFalsy(H)) return false; // explicit 0/false/no/off → bash
if (isTruthy(H)) return true; // explicit 1/true/yes/on → PowerShell
if (gitBashPath() === null) return true; // no Git Bash → PowerShell (reasonable)
return checkGate("tengu_cobalt_ridge", /*default*/ false); // ← else: experiment decides
}
Both the env-block string (qC() → "Shell: PowerShell …") and the tool registration (qC() ? [PowerShellTool] : []) key off this single function. With Git Bash present and CLAUDE_CODE_USE_POWERSHELL_TOOL unset, the decision is delegated entirely to the tengu_cobalt_ridge gate, which returned true for this account.
Two design problems:
- The gate never consults
process.env.SHELLor the launching shell. A user actively running in Git Bash (withSHELLpointing at bash) is still flipped to PowerShell. The only "is the user a bash user?" signal it uses is the binary "is Git Bash absent?" — which is the wrong question. - Silent enablement. An experiment that changes the execution shell and the tool schema ships with no one-time notice and no surfaced opt-out, so the user only discovers it by debugging wasted-token retry loops.
Expected
- When Git Bash is installed/detected and
SHELL/ the launching shell indicates a POSIX shell, the default should remain the Bash tool andShell: bash. The heuristic should weighSHELL, not just "is Git Bash missing?" - An experiment that flips the execution shell / tool schema should not enable silently — surface a one-time notice, or keep it behind the documented opt-in.
Actual
Silent default-enable via the tengu_cobalt_ridge gate; Shell: PowerShell advertised on a bash system; recurring wrong-shell tool calls and retries that burn tokens.
Impact
- Wasted tokens / iterations from wrong-syntax → retry loops.
- Cross-shell foot-guns for bash-based hooks and skills (cf. #59225).
Workaround (verified on 2.1.158)
In settings.json → env:
"CLAUDE_CODE_USE_POWERSHELL_TOOL": "false",
"SHELL": "/usr/bin/bash"
false matches the falsy branch → qC() returns false before the experiment is consulted → the PowerShell tool is de-registered and the env block reverts to Shell: bash (use Unix shell syntax …). SHELL makes that line render as bash rather than unknown. Confirmed: after restart the PowerShell tool is gone from the schema and the env block reads Shell: bash.
Related (not duplicates)
- #59225 — model told
Shell=PowerShellwhile hooks execute under bash. Closest existing issue; this report adds the experiment-gate root cause, the token-cost angle, and theSHELL-blind heuristic. - #59588 — docs still describe an opt-in rollout while the behavior is default-enable.
- #62537 / #63514 — the inverse symptom (PowerShell tool unexpectedly absent under Git Bash). Together with this report they suggest the Git-Bash ↔ PowerShell default heuristic needs a single, coherent,
SHELL-aware redesign rather than per-experiment toggling.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗