fix(host-service): classify a hollow nested-repository .git and a broken Xcode git shim as environmental
Problem
Two git failures that the host-service status adapter does not recognise, so git.getStatus returns a 500 and reports on every refresh. Both are the user's machine or repository, not our code:
- HOST-SERVICE-57 (3,249 events, one host, 1.25.1): every status call fails with
fatal: '<path>/.git' not recognized as a git repository. The user's Changes tab is unusable for that workspace, and status, diff and stage-all all fail the same way. - HOST-SERVICE-58 (80 events, one host, 1.26.0): every git call fails with a dyld error from xcodebuild followed by
xcode-select: Failed to locate 'git', requesting installation of command line developer tools.No git command can run on that Mac until Xcode or the Command Line Tools are reinstalled.
Reality check:
- Harm: the affected user has a dead Changes tab in every affected workspace, and on 1.25.1 the host reports every refresh (17 events in one 38-second burst, driven by
git:changedand window focus, not an interval). Nothing in our code can make either git call succeed. - Reach: both events come from the host-service bundled in the desktop app (
app.asar,git.getStatus). This change ships in the next desktop/host-service release and applies to the getStatus path both users hit. Machines on 1.25.1/1.26.0 keep reporting until they update, as with every host-service fix. - Why code: archiving is undone by the next machine; an inbound filter is forbidden by the #6164 contract; the existing adapter is exactly the place the repo classifies git's own text, and both wordings are unambiguous once traced to their source.
Root cause
- The "not recognized" wording is emitted by exactly one place in git:
is_submodule_modified()insubmodule.c, when a gitlink in the index has a.gitthat is a directory but not a repository (HEAD, objects or refs are gone). That is what a cloud-sync client leaves behind when it rewrites a nested repository's.git. Git opens every gitlink before it reports status or a diff and dies at the first bad one. Reproduced locally by replacing a nested repository's.gitwith an empty directory: status,diff, andadd -Aall die with this exact line,branch -msucceeds, which matches the procedure split in the Sentry group. The workspace itself resolved fine, so this is notNOT_GIT_REPO(git says "not a git repository:" for that, and the existing branch keeps it). It is repository damage the user has to repair, so it joins the unreadable-tree-object case underGIT_REPO_DAMAGED. - The Xcode wording is the
/usr/bin/gitstub: it asksxcodebuild -find git, xcodebuild fails to load its own libraries, and the stub prints this sentence instead of running git. Same condition as the existing no-developer-tools stub, worded for a broken Xcode install, so it joins that branch underGIT_ENVIRONMENT.
Fix
Two anchored patterns in classify-git-error.ts, each on an existing branch:
^fatal: '.*\/\.git' not recognized as a git repository$→PRECONDITION_FAILED/GIT_REPO_DAMAGED^xcode-select: Failed to locate 'git', requesting installation of command line developer tools\.$→PRECONDITION_FAILED/GIT_ENVIRONMENT
No new cause kind. No host-service consumer reads cause kinds today (the v2 renderer reads none; only the tests and the v1 desktop hook do), so a GIT_UNAVAILABLE kind here would be vocabulary nobody reads. The xcode-select case stays on the branch the existing xcode-select pattern already uses. Messages are preserved verbatim.
Negative cases written first and confirmed passing before the change: git's not a git repository: '<path>/.git' wording stays NOT_GIT_REPO; the stub naming another tool (swift) from a hook, the sentence without the stub prefix at line start, fatal: bad object HEAD, a merge conflict, a network failure, and a pathspec ending in .git all stay unclassified 500s. spawn git EAGAIN stays a 500 (existing test).
Still reports as a 500 after this change: every other git failure, including the same two conditions surfacing through procedures that do not run the classifier (git.renameBranch, git.stageAll, project.create each appear a handful of times in these groups). Those are adjacent throw sites left as they are.
Not touched: DESKTOP-73. That group is 2,671 spawn git EAGAIN events versus 25 spawn git ENOENT over 90 days, all macOS. EAGAIN is resource exhaustion and must keep reporting; the ENOENT slice is under 1% and on macOS means the PATH handed to git lacks /usr/bin, which is not "git isn't installed". Details in the session summary.
Verification
$ bunx biome check packages/host-service/src/trpc/router/git/utils/classify-git-error.ts packages/host-service/src/trpc/router/git/utils/classify-git-error.test.ts
Checked 2 files in 11ms. No fixes applied.
$ cd packages/host-service && bun run typecheck
$ tsc --noEmit --emitDeclarationOnly false
$ cd packages/host-service && bun test src/trpc/router/git/utils
102 pass
0 fail
Ran 102 tests across 8 files. [16.86s]
Before the change, the same test file ran with exactly the two new positive cases failing and all negatives passing:
(fail) rethrowEnvironmentalGitError > nested repository with a hollow .git → PRECONDITION_FAILED / GIT_REPO_DAMAGED
(fail) rethrowEnvironmentalGitError > Xcode installed but its git shim cannot run → PRECONDITION_FAILED / GIT_ENVIRONMENT
25 pass
2 fail
The verbatim Sentry strings (run outside the repo so no user path lands in the tree) classify as 57 → PRECONDITION_FAILED / GIT_REPO_DAMAGED, 58 → PRECONDITION_FAILED / GIT_ENVIRONMENT; spawn git EAGAIN and spawn git ENOENT remain unclassified.
Refs HOST-SERVICE-57
Refs HOST-SERVICE-58
https://claude.ai/code/session_01RU6TRLQj25fDjQ8WiukBUj
Summary by cubic
Fixes git.getStatus returning 500 on two environmental git failures: a nested repository with a hollow .git (cloud-sync damage) and a broken Xcode git shim. These are now classified as PRECONDITION_FAILED with GIT_REPO_DAMAGED and GIT_ENVIRONMENT cause kinds, so they stop appearing as server errors.
- Adds two anchored patterns to
classify-git-error.ts, matching git's exact wording and preserving messages verbatim. - Negative tests confirm unrelated failures (bad object, merge conflict, network, pathspec) and git's "not a git repository" wording remain unclassified.
- Resolves HOST-SERVICE-57 and HOST-SERVICE-58.
Summary by CodeRabbit
- Bug Fixes
- Improved Git error handling for damaged nested repositories and invalid
.gitdirectories. - Improved detection of macOS Git environment failures when Xcode cannot load required libraries.
- Preserved correct handling for non-repository paths, conflicts, network issues, and unsupported Git tools.
- Improved Git error handling for damaged nested repositories and invalid