Fixing Claude Code v2 Freezes on Windows Caused by Git Bash

Summary

A critical production issue was identified where Claude Code v2.x would freeze indefinitely when executing shell commands (such as ls, find, grep, or ./gradlew) on Windows environments. The application would hang without output, leading to the accumulation of multiple zombie bash processes that required a hard restart of the tool. The issue was eventually traced to an unintended shell selection mechanism that prioritized Git Bash (MSYS) over native Windows shells, causing severe performance degradation due to path translation overhead.

Root Cause

The failure was caused by an automated shell discovery logic that bypassed the user’s intended execution environment. When shellPath was not explicitly defined in the configuration, Claude Code followed a specific priority ladder:

  • Missing Configuration: The shellPath in ~/.claude/settings.json was empty.
  • Environment Variable Hijacking: The SHELL environment variable was set to Git Bash.
  • PATH Precedence: The first bash.exe found in the system PATH (located in C:\Program Files\Git\usr\bin\bash.exe) was selected as the primary execution engine.

This resulted in MSYS path translation overhead. Every time the agent attempted to access a file on the D:\ drive, the MSYS layer had to translate the Windows path to a Unix-style path (e.g., /d/path). On large projects or deep directory structures, this translation layer created a massive computational bottleneck, causing the commands to appear hung.

Why This Happens in Real Systems

This is a classic case of leaky abstractions and environment interference:

  • Implicit Defaults: Software often makes “smart” assumptions about the environment to improve user experience, but these assumptions fail in complex, multi-layered OS environments like Windows.
  • Path Translation Layers: Tools like MSYS, Cygwin, or WSL attempt to provide a Unix-like experience on Windows, but they introduce a translation shim that adds latency and can conflict with native Windows file system locks.
  • Priority Cascades: When a tool uses a fallback logic (Config $\rightarrow$ Env Var $\rightarrow$ PATH), a single misconfigured environment variable can trigger a cascade of incorrect behaviors that are difficult to debug.

Real-World Impact

  • Developer Velocity Loss: Engineers were forced to kill processes and restart tools manually, breaking flow state.
  • Resource Exhaustion: Each hang spawned a new background shell, leading to an accumulation of 9+ active, stuck shells, consuming system memory and process handles.
  • Silent Failures: Because the commands produced no stdout/stderr during the hang, it was impossible to distinguish between a slow command and a deadlocked process.
  • Tool Unreliability: The agent became unusable for any project involving large file trees or complex build tools like Gradle.

Example or Code (if necessary and relevant)

To resolve the issue, the internal shell must be explicitly pinned to a native Windows executable to bypass the MSYS discovery logic.

{
  "shellPath": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
  "env": {
    "SHELL": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
  }
}

How Senior Engineers Fix It

Senior engineers move away from implicit behavior toward explicit configuration:

  • Hardening Configurations: Instead of relying on the system PATH, they explicitly define the execution environment in configuration files to ensure idempotency across different machines.
  • Isolating Environments: They recognize that running Unix-like tools on Windows via emulated layers (MSYS) is a high-risk activity and prefer native execution or containerized environments (WSL2/Docker).
  • Observability: They would have investigated the process tree immediately to see that bash.exe was being spawned, rather than just looking at the Claude Code UI.

Why Juniors Miss It

  • Symptom-Focused Debugging: Juniors often focus on the “hang” itself (trying timeout or re-installing the app) rather than investigating why a specific process was chosen to execute the command.
  • Surface-Level Assumptions: A junior might assume that because they launched the app from PowerShell, the app is running in PowerShell. They miss the fact that the application can spawn sub-processes using entirely different shells.
  • Overlooking Environment Variables: They often neglect to check the global SHELL variable or the internal ~/.claude/settings.json for hidden configuration overrides.

Leave a Comment