Skip to content

fix(server): name the cause of a failed git command - #8645

Open
walid-baharwal wants to merge 1 commit into
pingdotgg:mainfrom
walid-baharwal:fix/git-error-stderr-excerpt
Open

fix(server): name the cause of a failed git command#8645
walid-baharwal wants to merge 1 commit into
pingdotgg:mainfrom
walid-baharwal:fix/git-error-stderr-excerpt

Conversation

@walid-baharwal

@walid-baharwal walid-baharwal commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

What Changed

GitCommandError gains an optional reason: a closed set of diagnostic tags for well-known
git failures, recognized from stderr inside the driver and appended to the message the way
EnvironmentInternalError does it. The tag names the cause; it selects no text.

Before and after, for the same failing command (real git, captured from the driver):

Git command failed in GitVcsDriver.createWorktree (/tmp/…/repo): git worktree add failed
Git command failed in GitVcsDriver.createWorktree (/tmp/…/repo): git worktree add failed (branch_checked_out_in_worktree)

Raw stderr still never leaves the driver.

Why

Fixes #4380. Every git precondition failure collapsed into one string, so a tag conflict, an
auth failure and a branch already checked out elsewhere were indistinguishable without
re-running the command by hand.

The issue offers two options. The first — carrying a bounded stderrExcerpt — is the one I
did not take: apps/server/src/vcs/GitVcsDriverCore.test.ts already asserts
notProperty(error, "stderr") and that a secret passed in argv never reaches
error.message, so dropping stderr is deliberate, and shipping an excerpt would mean
deleting a security test. This is the issue's second option, the parsed reason, which keeps
that guarantee intact: the tags are a closed literal union, the sentences are static, and
nothing matched from stderr is ever interpolated. The existing redaction test is extended
with notProperty(error, "reason").

Classification happens at the two shared funnels every git call routes through
(executeGit and the non-zero-exit branch of the raw executor), which covers
fetchRemote and createWorktree — the two operations named in the issue.

Two limits, stated rather than hidden:

  • Only git's own diagnostic lines are classified, plus the refusals ssh prints. remote:
    is deliberately excluded: git prefixes every byte the server sends that way, remote hook
    output included, so trusting it would reintroduce the same false positive from the far end.
    Every source that can reach the classifier is covered by a test — local hook output, remote
    hook output, five ssh refusal shapes, and an OS-level permission error that must not read as
    ssh auth. The one accepted residue is that a local hook echoing an ssh refusal verbatim would
    still be classified; that text is narrow enough that the tag would arguably still be right. Hooks write to the same stream
    unprefixed, so a pre-push hook echoing "authentication failed" would otherwise be reported
    as a credential failure (reproduced, and now covered by a test that pushes through a failing
    hook). ssh states a key refusal unprefixed as well, so those specific lines stay eligible —
    otherwise git's generic "could not read from remote repository" would be read as an
    unreachable remote when the real cause is credentials.
  • Classification is English-only. executeGit inherits the process locale, so under a
    non-English LANG git's wording does not match and the error simply keeps today's
    behavior — no reason, no regression. Forcing LC_ALL=C for every git command would fix
    that, but it changes the environment of every call in the driver and belongs in its own
    change. The new tests pin LC_ALL: "C" so they do not depend on the runner's locale.
  • Five other GitCommandError constructions hold stderr in hand and stay unclassified.
    Adding them is one line each and deliberately left out to keep this to one concern.

UI Changes

None. Server-side error metadata; no rendered change. The improved text surfaces wherever
an existing git error message is already shown.

Verification

vp test run apps/server/src/vcs/GitVcsDriverCore.test.ts   # 68 passed
cd packages/contracts && tsgo --noEmit                     # clean
cd apps/server && tsgo --noEmit                            # clean
vp lint apps/server/src/vcs/GitVcsDriverCore.ts apps/server/src/vcs/GitVcsDriverCore.test.ts packages/contracts/src/git.ts
vp format --check <same three files>
git diff --check

The new tests drive real git through the real driver: a branch already checked out in
another worktree, a command outside a repository, a tag collision that must stay unclassified, a failing
pre-push hook whose output must not be classified, and a fallback detail that must not run
into the reason sentence. Each fails without the source change.

Checklist

  • One concern
  • Focused tests, failing before the fix
  • Typecheck, lint and format run on the changed packages
  • No new dependencies, no committed artifacts
  • Additive optional contract field; web, mobile and desktop need no change (no reference
    to GitCommandError fields exists in any client)

Model: Claude Opus 5 (1M context). Harness: Claude Code.


Note

Medium Risk
Changes how git failures are surfaced (new optional field and message suffix) and classifies auth/SSH-related stderr, though patterns are conservative and stderr still never leaves the driver.

Overview
GitCommandError now carries an optional reason from a closed GitCommandFailureReason union in contracts, and the error message appends (reason) when classification succeeds—without ever exposing raw stderr.

The git VCS driver adds classifyGitFailure, which pattern-matches filtered diagnostic lines (plus specific SSH refusal lines) at the shared non-zero-exit paths in executeRaw and executeGit. Callers can distinguish worktree branch conflicts, missing repos, auth vs host-key failures, and similar cases while the redaction guarantee stays intact (no stderr, no matched text in messages). Classification deliberately ignores hook/remote: noise and leaves ambiguous failures (e.g. duplicate tags) untagged; matching is English-only unless git runs under LC_ALL=C.

Integration tests exercise real git scenarios and extend the secret-leak test to assert reason is not set when stderr would be unsafe to interpret.

Reviewed by Cursor Bugbot for commit 61b8981. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add reason tag to GitCommandError for classified git failures

  • Introduces GitCommandFailureReason schema in git.ts with literal tags like authentication_failed, not_a_repository, branch_checked_out_in_worktree, and host_key_unverified.
  • Adds classifyGitFailure(stderr) in GitVcsDriverCore.ts that matches stderr lines against ordered regex patterns to produce a reason tag, returning null when no pattern matches.
  • Both executeRaw and executeGitWithStableDiagnostics now call classifyGitFailure on non-zero exits and attach the reason to the thrown GitCommandError.
  • GitCommandError.message now appends (reason) when a reason is present, so callers that parse error messages see a new suffix.
  • Risk: any code that string-matches GitCommandError messages or asserts exact error text will break due to the appended (reason) suffix; the reason field is optional so existing .reason checks are unaffected.

Macroscope summarized 61b8981.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f865d56-f9eb-46c4-9443-de0b394614e4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


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

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 29, 2026
Comment thread apps/server/src/vcs/GitVcsDriverCore.ts
Comment thread packages/contracts/src/git.ts
@macroscopeapp

macroscopeapp Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR changes shared production Git error handling and automatically classifies SSH authentication and host-key failures, with new metadata and message output visible to existing callers. The authentication-sensitive runtime behavior warrants human validation despite the additive contract and comprehensive integration tests.

You can add or adjust custom eligibility rules. Learn more.

@walid-baharwal
walid-baharwal force-pushed the fix/git-error-stderr-excerpt branch from 64e5038 to dc39834 Compare August 29, 2026 20:45
Comment thread apps/server/src/vcs/GitVcsDriverCore.ts
@walid-baharwal
walid-baharwal force-pushed the fix/git-error-stderr-excerpt branch from dc39834 to 87e512e Compare August 30, 2026 18:14
Comment thread apps/server/src/vcs/GitVcsDriverCore.ts
Comment thread apps/server/src/vcs/GitVcsDriverCore.ts
@walid-baharwal
walid-baharwal force-pushed the fix/git-error-stderr-excerpt branch from 87e512e to 59c8b8b Compare August 30, 2026 18:26

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One convention finding on the new GitCommandError.reason discriminator. Details inline.

Posted via Macroscope — Effect Service Conventions

Comment thread packages/contracts/src/git.ts Outdated
@walid-baharwal
walid-baharwal force-pushed the fix/git-error-stderr-excerpt branch from 59c8b8b to 3a9c452 Compare August 30, 2026 18:54

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3a9c452. Configure here.

Comment thread apps/server/src/vcs/GitVcsDriverCore.ts Outdated
Git states why a command failed on stderr, but stderr is deliberately kept
off GitCommandError — it echoes argv and remote URLs, which can carry
credentials. Callers were left with "git fetch origin failed" and no way to
tell a tag conflict from an auth failure without re-running git by hand.

Match stderr at the driver against a fixed set of well-known failures and
carry the result as a closed set of diagnostic tags, appended to the message
the way EnvironmentInternalError does it. The tag names the cause; it selects
no text, so nothing matched from stderr is ever quoted and the existing
redaction guarantee is unchanged.

Only git's own diagnostic lines are classified, plus the refusals ssh prints.
Hooks write to the same stream unprefixed, so a pre-push hook echoing
"authentication failed" would otherwise be reported as a credential failure.
ssh states its refusal unprefixed too, naming whichever methods it tried, and
git adds only a generic "could not read from remote repository" afterwards —
so every refusal shape is matched, and an untrusted host key gets its own tag
rather than being blamed on credentials.

Fixes pingdotgg#4380
@walid-baharwal
walid-baharwal force-pushed the fix/git-error-stderr-excerpt branch from 3a9c452 to 61b8981 Compare August 30, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Git command errors discard stderr, making failures opaque to callers

1 participant