Skip to content

fix(host-service): pin git's message locale so environmental failures classify - #6863

Merged
saddlepaddle merged 1 commit into
mainfrom
sentry/git-message-locale
Aug 25, 2026
Merged

saddlepaddle merged 1 commit into
mainfrom
sentry/git-message-locale

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Aug 25, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A workspace status poll runs git in a directory that is not a git repository. We already decided that is a user-environment condition, not a bug: getStatus catches the failure and rethrows it as a typed non-500 whenever it recognises git's own wording. The recognition is a set of anchored English regular expressions.

Git translates its diagnostics into the user's locale. For anyone not running an English environment the text never matches, the failure falls through unclassified, and the same condition that is silent for an English user reports as a 500. The captured message in HOST-SERVICE-3Q is the French rendering of git's "not a git repository" text — 2,166 events over 7 days from three machines, none of them English-locale.

This is not specific to that one pattern. Every pattern in the classifier is English-only, so the gap applies to all of them, and to every pattern added later.

Reality check (asked of every fix on this path):

  1. User-visible harm. Not in the UI — the Changes tab renders the same empty state either way, and the client's retry policy does not branch on the code. The harm is that a deliberate, already-shipped classification silently works for some users and not others: for a non-English user, every environmental git condition lands in the same unclassified bucket as a genuine host-service bug, so we cannot tell the two apart for exactly those users. A real regression affecting a French or Japanese machine is indistinguishable from their worktree having been deleted.
  2. Reach. The events come from git.getStatus on 1.23.0; that path is unchanged on main at 1.24.2. getStatus → runStatusSnapshot → resolveGitTaskEnv → createGitEnvResolver is the only env the status subprocesses get, and rethrowEnvironmentalGitError has exactly one host-service call site (git.ts:246), on that path. The change ships in the next host-service/desktop release and reaches every host on it.
  3. Why code. A Sentry-side filter is forbidden by repo law (PR fix(desktop,host-service): translate expected errors at throw sites, keep the Sentry boundary dumb #6164) and would also hide genuine bugs from these users. Translated patterns are unbounded, wrong the moment a locale is missing, and every entry is a fresh chance to over-match. Archiving leaves the classifier locale-dependent and re-fires. The code path is core and not being restructured. Fixing what git says is one variable and makes every existing pattern correct for everyone.
  4. Smallest change. One environment variable on the env resolver. No new patterns, no new matcher, no new typed cause, no new capture.
  5. New matcher? None. The classifier is untouched, so there is no new over-match surface; the existing negative tests still bound it.

Root cause

createGitEnvResolver builds the env every git invocation is handed. It spreads the user's own environment through the credential provider, so the user's LANG/LC_ALL/LANGUAGE reach git and git answers in that language. Nothing pins the locale.

Fix

Add LC_ALL: "C" alongside GIT_OPTIONAL_LOCKS in the env createGitEnvResolver returns, after the credential spreads so it wins over the user's own value.

LC_ALL rather than LC_MESSAGES. Both were live options; this is why the whole-locale one is right here:

  • LC_MESSAGES loses to the user. POSIX resolves LC_ALL ahead of LC_MESSAGES, so on a machine that exports LC_ALL=fr_FR.UTF-8 an LC_MESSAGES we set is simply ignored — the fix would silently not work for a subset of the very users it targets. Making it work would mean deleting the user's LC_ALL from the child env, which changes the effective value of every other category as a side effect: a strictly larger behaviour change than pinning one variable.
  • LANGUAGE outranks both, and glibc's gettext ignores it only when the resolved locale is exactly "C" (guess_category_value compares strcmp(locale, "C")). LC_ALL=C therefore neutralises LANGUAGE too; LC_ALL=POSIX would not, which is why the value is C.
  • The cost usually charged against LC_ALL is not real for git. Pinning LC_CTYPE sounds like it should change how non-ASCII path names are decoded and quoted in the output we parse. It does not: git reports paths as bytes and quotes them by core.quotePath, not by locale, and the snapshot reads status, numstat and untracked expansion through -z/--null, which disables quoting outright. Verified byte-for-byte on a repo with accented, CJK and Cyrillic filenames — git status --porcelain --null, git diff --numstat -z and git ls-files produce identical output under LC_ALL=C and LC_ALL=fr_FR.UTF-8. There is a test pinning this.

No typed cause added. The change adds no new throw, so no new cause.kind. (The existing kinds on this classifier are read by nothing but its own tests; that is pre-existing and out of scope here.)

Behaviour change worth naming: git's own prose in error surfaces is now English for non-English users rather than their language. The app UI is English-only, so this is a consistency gain, but it is a real user-visible difference.

Which git failures on this path still report as 500s

Everything the classifier does not name — now reliably in English, so the boundary is the same on every machine. From the classifier's own negative tests: index.lock contention, dubious ownership, bad revisions and pathspecs, cannot chdir to work tree, installed filters that ran and failed (external filter ... failed), unable to read for non-tree objects, spawn syscall failures (spawn git EAGAIN), and our own simple-git misconfiguration other than a missing base directory. Those are bugs or conditions we have not yet decided about, and they keep reporting.

Also unchanged: host-service builds a few git clients with no env override at all (project import, repo resolution, git identity). They still inherit the user's locale — nothing classifies their messages, so pinning them would be machinery for a hypothetical. They are named, not touched.

Verification

Every new test was written first and confirmed failing before the fix. The status-path test reproduces the Sentry message end-to-end — before the change:

121 |     expect(error.message).toContain("not a git repository");
error: expect(received).toContain(expected)
Expected to contain: "not a git repository"
Received: "fatal: ce n'est pas un dépôt git (ni aucun des répertoires parents) : .git\n"
(fail) git message locale > a localized 'not a git repository' failure classifies as a non-500
 1 pass
 3 fail

The one test that passed before and after is the negative control: it asserts that the unpinned env still produces the French wording and that the classifier does not match it. That is the standing record of why this is fixed in the environment rather than with a French pattern.

CI runs on machines whose git has no translation catalogs, so the localized case is driven against a git stub that resolves the message locale exactly the way GNU gettext does (LC_ALL over LC_MESSAGES over LANG; LANGUAGE consulted only when the resolved locale is not "C") and answers in that language. The stub is reached the way a real git is — through the child environment's PATH — so the resolver, createUserSimpleGit, the real getGitStatusSnapshot and the real classifier are all exercised.

After the change:

$ bunx biome check packages/host-service/src/runtime/git/git.ts \
    packages/host-service/src/runtime/git/git.test.ts \
    packages/host-service/src/runtime/git/git-message-locale.test.ts
Checked 3 files in 18ms. No fixes applied.
$ cd packages/host-service && bun run typecheck
$ tsc --noEmit --emitDeclarationOnly false
(exit 0)
$ bun test src/runtime/git/git-message-locale.test.ts
 4 pass
 0 fail
 9 expect() calls
Ran 4 tests across 1 file. [1334.00ms]
$ bun test src/runtime/git/ src/trpc/router/git/ src/workers/
 179 pass
 0 fail
 400 expect() calls
Ran 179 tests across 18 files. [45.80s]

Full package suite, since the env resolver feeds every git worker task:

$ cd packages/host-service && bun run test
 1227 pass
 8 todo
 0 fail
 2864 expect() calls
Ran 1235 tests across 117 files. [196.83s]

No desktop git code was touched, so apps/desktop/src/no-main-process-blocking.test.ts is not in scope. packages/host-service/src/no-main-loop-blocking.test.ts passes unchanged — the change is inside runtime/git/, and no git client was added.

Adjacent, deliberately not touched

apps/desktop/src/lib/trpc/routers/workspaces/utils/git-errors.ts carries a second copy of the same English-only patterns for the desktop process, with its own git clients and its own env. It has the same locale gap and is left alone here.

Refs HOST-SERVICE-3Q

https://claude.ai/code/session_018RT6cwVgxwCJ2MpnZaVpJz

Summary by CodeRabbit

  • Bug Fixes

    • Improved Git error handling by ensuring diagnostic messages are consistently interpreted across system locales.
    • Preserved non-ASCII filenames when reading Git status information.
    • Git repository errors are now classified reliably even when the operating system uses a non-English locale.
  • Tests

    • Added coverage for localized Git diagnostics and Unicode file paths.

… classify

Git translates its diagnostics into the user's locale, but the classifier
that keeps environmental git failures off the 500 path recognises git's own
English wording. For anyone not on an English-locale machine the wording
never matches, so a condition that is silent for an English user reports as
a 500 — HOST-SERVICE-3Q, 2,166 events in 7 days from three machines.

Pin LC_ALL=C on the env every git invocation is handed, so the anchored
patterns that already exist match for every user. No new patterns.

Claude-Session: https://claude.ai/code/session_018RT6cwVgxwCJ2MpnZaVpJz
@coderabbitai

coderabbitai Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 20bbec19-53cf-4729-b6ac-6cbfb5434715

📥 Commits

Reviewing files that changed from the base of the PR and between b1e10c3 and a28da5d.

📒 Files selected for processing (3)
  • packages/host-service/src/runtime/git/git-message-locale.test.ts
  • packages/host-service/src/runtime/git/git.test.ts
  • packages/host-service/src/runtime/git/git.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Git invocations now force LC_ALL=C. Tests verify consistent Git error classification and preservation of Unicode filenames during status parsing.

Changes

Git locale normalization

Layer / File(s) Summary
Git invocation environment
packages/host-service/src/runtime/git/git.ts, packages/host-service/src/runtime/git/git.test.ts
The Git environment resolver sets LC_ALL to "C". Unit tests verify the merged environment.
Locale integration coverage
packages/host-service/src/runtime/git/git-message-locale.test.ts
Integration tests verify localized repository errors classify as BAD_REQUEST/NOT_GIT_REPO, unpinned French diagnostics remain unclassified, and Unicode status paths remain unchanged.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to a28da

The change pins Git diagnostics to a consistent locale so environmental failures classify uniformly across users; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: kitenite

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commit format and clearly summarizes the main change: pinning Git's message locale so environmental failures classify consistently.
Description check ✅ Passed The description clearly explains the problem, root cause, fix, behavior impact, scope, and verification results. It does not use the exact template headings or include a Checklist section, but it prov…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description clearly explains the problem, root cause, fix, behavior impact, scope, and verification results. It does not use the exact template headings or include a Checklist section, but it provides the required substantive information and is mostly complete.

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentry/git-message-locale

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@saddlepaddle
saddlepaddle merged commit 2068955 into main Aug 25, 2026
20 checks passed

This branch was previously deployed

1 inactive deployment
preview — a28da5de Deployed Aug 25, 2026 by saddlepaddle via Deploy Web #8472
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.

1 participant