I hit a frustrating edge case the other day: the Claude Code panel inside the Claude desktop app repeatedly popped up a modal asking me to install Git.
Git is required to run local sessions. Run
xcode-select --installin Terminal to install the Command Line Tools, or download Git directly — or switch to a remote environment.
The catch? Git was already installed.
Running git --version in my terminal returned a valid version immediately. Claude Code ran seamlessly from the command line. It was only the desktop GUI app that refused to acknowledge Git’s existence.
Since I was running the macOS 27 beta, my immediate assumption was “beta bug.” That single assumption cost me an hour of rabbit holes. Here is the complete post-mortem, wrong turns included.
First Guess: Broken Command Line Tools
On macOS, /usr/bin/git isn’t actually the Git binary — it’s a shim launcher that forwards commands to whichever developer directory xcode-select currently points to. Major macOS upgrades frequently break or unset this pointer, making a stale Command Line Tools installation the textbook culprit whenever Git suddenly “disappears” after an OS update.
Because direct package updates for beta OS releases aren’t always immediately served via Software Update, I downloaded the .dmg directly from the Apple Developer Downloads page, installed it, and manually reset the developer directory:
sudo xcode-select -s /Library/Developer/CommandLineTools
git --version # git version 2.54.0 (Apple Git-157)
clang --version # Apple clang version 21.0.0
Everything checked out in the CLI: git worked, clang worked.
Yet, after re-opening the desktop app, the dialog remained stubbornly present.
If Git is functioning in the shell but invisible to the GUI, the issue wasn’t Git itself — it was how the application resolves environment binaries.
Second Guess: The GUI PATH Environment
This is a classic macOS tripwire. Apps launched from Finder, Dock, or Spotlight do not inherit environment variables or custom PATH definitions set in your shell startup scripts (.zshrc or .bash_profile). GUI applications launch inside a minimalist launchd context. A binary easily resolved by your shell can be completely invisible to a GUI app.
Furthermore, Claude’s desktop app caches its environment at startup. If you fix a path issue while the app is active, the app’s view of the system remains stale until a hard restart (right-clicking the Dock icon → Quit, rather than merely closing the window).
I checked my system’s default launchd configuration:
launchctl getenv PATH
# (empty)
It returned empty. So I explicitly set a baseline PATH for launchd:
sudo launchctl config user path "/Library/Developer/CommandLineTools/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
After a full system reboot, launchctl getenv PATH verified the updated environment. I relaunched Claude Desktop, fully expecting victory.
The exact same dialog appeared.
At this point, I had diagnosed and fixed two legitimate system issues — a broken CLI tools pointer and a sparse GUI environment. Both were worth fixing, yet neither addressed the root cause.
The Root Cause: A Dead Symlink from 2022
I stopped guessing and went digging through actual process logs. Checking GitHub issue (#54754) for this exact behavior revealed the underlying failure mode:
Failed to spawn /Users/xxx/bin/git: spawn /Users/xxx/bin/git EACCES
The app wasn’t failing to find Git — it was finding a broken Git entry first in the system execution order and failing with an EACCES (Permission Denied) error when trying to execute it.
When a desktop app spawns a subprocess, it attempts to execute the very first match it discovers in the resolution path. If that candidate fails or lacks execution permissions, the process aborts immediately rather than falling through to subsequent matches like an interactive shell might.
I ran a resolution check to see every registered git binary path:
which -a git
# /usr/local/bin/git
# /usr/bin/git
There it was: /usr/local/bin/git preceded the standard system path /usr/bin/git.
Inspecting that file revealed its origin:
ls -la /usr/local/bin/git
# lrwxr-xr-x 1 epona admin 28 Feb 8 2022 /usr/local/bin/git -> ../Cellar/git/2.35.1/bin/git
It was a lingering symlink created in February 2022. It pointed to ../Cellar/git/2.35.1/, an artifact from an old Intel-based Homebrew installation that had been wiped years ago. (/usr/local is the legacy x86 Homebrew path; Apple Silicon Homebrew operates under /opt/homebrew).
Because the target path no longer existed, attempting to run this broken reference directly threw an error.
The resolution required a single cleanup command:
sudo rm /usr/local/bin/git
Verifying the active paths again:
which -a git
# /usr/bin/git
git --version
# git version 2.54.0 (Apple Git-157)
I force-quit the desktop application and relaunched it. The prompt disappeared completely.
Key Takeaways
- Shells mask broken paths; GUI apps don’t. Interactive shells can hide orphaned binaries depending on alias configurations and environment handling, but spawn-based application calls will fail hard on the first invalid entry they encounter.
- Use
which -a, not justwhich. Running a basicwhich gitonly shows the active path, whereaswhich -alists all instances across your environment in order of precedence — making shadowed or dead binaries instantly visible. - Check the application error logs early. Fixing intermediate environment issues (like Command Line Tools or
launchctlvariables) felt productive, but reading the actual subprocess spawn log directly pinpointed the precise path failure within seconds.
If a GUI developer tool reports that a CLI binary is missing despite working perfectly in your terminal, run which -a <tool> — chances are, an old symlink is standing first in line.