[BUG] Windows + Git Bash: tengu_cobalt_ridge experiment silently force-enables the PowerShell tool & "Shell: PowerShell", causing wrong-shell retries (token waste)

Resolved 💬 2 comments Opened May 31, 2026 by Meme-Theory Closed Jul 3, 2026

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_PATH set to a valid bash.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:

  1. The gate never consults process.env.SHELL or the launching shell. A user actively running in Git Bash (with SHELL pointing 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.
  2. 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 and Shell: bash. The heuristic should weigh SHELL, 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.jsonenv:

"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=PowerShell while hooks execute under bash. Closest existing issue; this report adds the experiment-gate root cause, the token-cost angle, and the SHELL-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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗