Skip to content

fix(macos): never run Apple's placeholder git, which opens the developer tools window - #779

Merged
Ark0N merged 1 commit into
masterfrom
fix/macos-git-placeholder-prompt
Nov 8, 2026
Merged

Ark0N merged 1 commit into
masterfrom
fix/macos-git-placeholder-prompt

Conversation

@Ark0N

@Ark0N Ark0N commented Nov 6, 2026

Copy link
Copy Markdown
Owner

What this is

On a Mac without the developer tools (no Xcode, no Command Line Tools), /usr/bin/git is not git. It is a placeholder from Apple: running it, even as git --version, makes macOS open its "The git command requires the command line developer tools. Would you like to install the tools now?" window.

The Codeman server ran exactly that. When a page is rendered, the server collects which tools exist on the machine, and for git it called isGitAvailable(), which runs git --version. So on a Mac without the developer tools:

  • a no-admin install (install.sh --no-admin, shipped in 1.49.0) finished cleanly, and one second after the server started, on the first page load, that system window appeared;
  • because the answer is only remembered for the life of the process, it would appear again after every server start, so at every login.

The installer itself already avoids the placeholder for this reason. The server did not.

This PR puts one check in front of every place where the server starts git. It changes nothing on Linux, and nothing on a Mac that has the developer tools or another git (Homebrew, for example) first on the PATH.

How it was found

On a new MacBook (macOS 27.2, no developer tools, no Homebrew), installing Codeman without administrator rights. The install took about 25 seconds and reported no error. The system window opened right after. The system log names the Codeman server as the process that asked:

14:14:43  server answers on 127.0.0.1:3000
14:14:44  git[15194]  Command Line Tools installation request ... parent PID 15056   (15056 = node dist/index.js web)

The fix

New module src/utils/git-availability.ts, with the decision as pure functions and two small IO steps around it:

  1. On macOS only, find which git the server's PATH resolves to, without running it.
  2. If that is /usr/bin/git, ask xcode-select -p whether a developer folder is selected. xcode-select is a real program that prints and exits; it never opens a window. The folder it names must also contain a git of its own (usr/bin/git, true for Xcode and for the Command Line Tools), so a selected Xcode that was deleted later does not count.
  3. No developer tools: git counts as absent and is not started. The caller gets the same ENOENT it would get on a machine with no git at all, which every caller already answers with "git is not installed".

A "yes" is kept for the life of the process. A "no" is asked again after 60 seconds, so installing the developer tools later needs no server restart for the git features. isGitAvailable() keeps its memo for a real answer but no longer memoizes this one refusal.

Every git the server starts now goes through gitSpawnAllowed(): cloning a repository as a case, git worktrees, the Git panel's status and fetch, the update check and repository status, and Read My Mind's workspace signals.

Reviewing the diff

371 lines added, 1 removed, in 10 files. Most of it is two new files:

Part Lines What to look at
src/utils/git-availability.ts (new) 147 The whole fix. gitSpawnWouldPrompt() is the decision; gitSpawnAllowed() is the memoized entry point.
test/git-spawn-gate.test.ts (new) 181 Unit tests for the decision, plus a scan of src/ that fails when a file starts git without calling the gate. The reviewed list of call sites is pinned in the test, so a new one fails until someone looks at it.
Seven call sites 3 to 21 each One guard line (or one early return in the caller's own result shape) in front of each spawn('git') / execFile('git'). No other behaviour changes. src/git-clone.ts is the largest because isGitAvailable()'s comment grew.
CLAUDE.md 1 The rule, under Common Gotchas.

How it was checked

  • npm run typecheck, npm run lint, Prettier on the changed files: clean.
  • The new test and the tests of every touched module (git clone, fetch, status, worktrees, repository status, self-update, Read My Mind collectors): 307 passed.
  • Full npm test: 14684 passed, 1 failed, 12 skipped. The one failure was test/frontend-public-tooling.test.ts in a fresh worktree that lacked the generated spreadsheet preview assets (npm install creates them); with the assets in place it passes, and it does not touch this change.
  • On real Macs with the developer tools (Command Line Tools on macOS 27.2, Xcode on another): the gate resolves /usr/bin/git, finds the tools and allows git, in 3 to 5 ms.
  • On the MacBook above with the developer tools removed again: Codeman uninstalled, then a fresh no-admin install of a build with this fix. The server started, the page loaded with git reported as absent, no window opened, and the system log holds no request to install the tools.

Not in this PR

  • No changeset: the release adds it.
  • The same Mac showed that Claude Code and other agent CLIs may run git themselves inside a session. That is their own behaviour and is not changed here.

…per tools window

On a Mac without the developer tools, /usr/bin/git is a placeholder: running
it, even as `git --version`, makes macOS open its "install the command line
developer tools" window. The server asked "is git here?" exactly that way on
the first page load (isGitAvailable() from renderIndexHtml), so a no-admin
install on such a Mac ended with that window one second after the server
started, and it would have come back at every start. Found on a new MacBook
on 2026-11-06: the system log names the Codeman server as the parent of that
git process.

One gate now stands in front of every git the server starts
(src/utils/git-availability.ts). On macOS it resolves git on the PATH itself;
when that is the placeholder, `xcode-select -p` must name a developer folder
that holds a git of its own. xcode-select only prints and never opens a
window. Without the tools, git counts as absent and is never started: every
caller gets the ENOENT it already answers with "git is not installed".
A refusal is asked again after a minute, so installing the tools later needs
no server restart for the git features, and isGitAvailable() no longer
memoizes that one refusal.

Gated: the clone and worktree runners, the Git panel's status and fetch, the
update check and repository status, and Read My Mind's workspace signals.
test/git-spawn-gate.test.ts covers the decision and scans src/ for a git spawn
in a file that does not call the gate, with the reviewed list of sites pinned.

Checked for real on macOS 27. With the developer tools present (Command
Line Tools on one Mac, Xcode on another) the gate resolves /usr/bin/git,
finds the tools and allows git, in 3 to 5 ms. On a MacBook with no
developer tools, a fresh no-admin install with this fix started the server
and loaded the page with git reported as absent, no window opened, and the
system log holds no request to install the tools; the same install without
the fix logged that request one second after the server started.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants