Skip to content

fix: degrade gracefully when git is unavailable or diff base ref is missing#930

Merged
rayhanadev merged 4 commits into
mainfrom
cursor/triage-923-c4f7
Jun 22, 2026
Merged

fix: degrade gracefully when git is unavailable or diff base ref is missing#930
rayhanadev merged 4 commits into
mainfrom
cursor/triage-923-c4f7

Conversation

@skoshx

@skoshx skoshx commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a crash when git is not installed or the --diff base ref isn't present in the checkout (both common in CI containers and shallow clones). When git is unavailable, the scan now throws GitBaseBranchMissing (an expected user error) instead of GitInvocationFailed, preventing Sentry spam while still giving users a clear error message.

Closes #923

Root Cause

Two separable conditions caused Sentry-reported crashes (REACT-DOCTOR-F (76 events), REACT-DOCTOR-1K, REACT-DOCTOR-14, REACT-DOCTOR-22):

  1. Git binary missing: The spawn fails with ENOENTPlatformError → wrapped in GitInvocationFailed → reported to Sentry
  2. Base ref not resolvable: git rev-parse --verify <base> exits non-zero, but the error was wrapped in GitInvocationFailed instead of GitBaseBranchMissing

Both threw ReactDoctorError({ reason: GitInvocationFailed }) which was NOT in isExpectedUserError, so it was reported to Sentry as a crash.

Fix Approach

The key insight: both "git missing" and "ref doesn't exist" should surface as GitBaseBranchMissing (an expected user error that triggers the existing fallback-to-full-scan logic and is NOT reported to Sentry).

Before:

  • Git missing → GitInvocationFailed → reported to Sentry ❌
  • Ref doesn't exist → varies, sometimes GitInvocationFailed → reported to Sentry ❌

After:

  • Git missing → branchExists catches GitInvocationFailed, returns falseGitBaseBranchMissing → clean user error, NOT reported to Sentry ✅
  • Ref doesn't exist → branchExists returns falseGitBaseBranchMissing → clean user error, NOT reported to Sentry ✅

Changes

Core (@react-doctor/core)

  • branchExists(): Now catches GitInvocationFailed (git binary missing) and returns false instead of propagating the error
  • diffSelection() and resolveDiffRange(): Unchanged - still throw GitBaseBranchMissing when branchExists returns false, maintaining existing behavior

CLI (react-doctor)

  • warnDiffUnavailable(): Updated message to mention "git unavailable, ref not found, or merge-base failed"
  • isExpectedUserError(): Already handles GitBaseBranchMissing (no changes needed)

Tests

  • Updated git-missing-binary.test.ts: Verifies that git unavailability converts to GitBaseBranchMissing
  • Removed redundant git-missing-ref.test.ts (covered by existing diff-commit-range.test.ts)

Behavior

Users see the SAME error message in both cases:

Diff base branch "main" does not exist (run `git fetch` to update remote refs). Running full scan.

The difference is internal: previously GitInvocationFailed was reported to Sentry; now GitBaseBranchMissing triggers the existing clean-error path.

Testing

  • ✅ All existing tests pass (including diff-commit-range.test.ts which expects the throw)
  • ✅ New tests verify the git-missing → GitBaseBranchMissing conversion
  • pnpm test, pnpm lint, pnpm typecheck all pass
  • ✅ Changeset added (patch)
Open in Web Open in Cursor 

cursoragent and others added 2 commits June 21, 2026 22:14
…issing

Closes REACT-DOCTOR-F, REACT-DOCTOR-1K, REACT-DOCTOR-14, REACT-DOCTOR-22

When git binary is not installed or the diff base ref cannot be resolved,
the scan now falls back to a full scan with a clear warning instead of
crashing and reporting to Sentry.

Changes:
- Modified branchExists() to catch GitInvocationFailed and return false
  instead of propagating the error
- Modified diffSelection() to return null when branchExists returns false
  for an explicit base, allowing graceful degradation
- Updated warnDiffUnavailable() to mention git unavailability and missing
  refs as possible causes
- Added comprehensive tests for both git-missing and ref-missing scenarios

Co-authored-by: Skosh <skoshx@users.noreply.github.com>
Co-authored-by: Skosh <skoshx@users.noreply.github.com>
@pkg-pr-new

pkg-pr-new Bot commented Jun 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/eslint-plugin-react-doctor@930
npm i https://pkg.pr.new/oxlint-plugin-react-doctor@930
npm i https://pkg.pr.new/react-doctor@930

commit: 3553740

The initial fix was too broad - it converted missing refs to null, but the
existing behavior (throwing GitBaseBranchMissing) is correct for user errors.
The key fix is that branchExists() now catches GitInvocationFailed (git
binary missing) and converts it to false, which then becomes
GitBaseBranchMissing - an expected user error that doesn't report to Sentry.

- Git missing → branchExists returns false → GitBaseBranchMissing → user error
- Ref doesn't exist → branchExists returns false → GitBaseBranchMissing → user error
Both are now handled the same way without Sentry spam.

Co-authored-by: Skosh <skoshx@users.noreply.github.com>
The graceful-degradation work dropped the comment explaining why
`currentBranch` swallows a git-spawn failure to null (so auto-detect degrades
instead of crashing). The behavior is unchanged and still load-bearing, so the
rationale is worth keeping next to it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rayhanadev rayhanadev marked this pull request as ready for review June 22, 2026 06:13
@rayhanadev rayhanadev merged commit ea4d9af into main Jun 22, 2026
22 checks passed
@rayhanadev rayhanadev deleted the cursor/triage-923-c4f7 branch June 22, 2026 06:13

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +441 to +445
Effect.catch((error) =>
error.reason._tag === "GitInvocationFailed"
? Effect.succeed(false)
: Effect.fail(error),
),

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.

🟡 AGENTS.md violation: Manual _tag dispatch inside Effect.catch instead of Effect.catchReasons

The AGENTS.md mandates: "Effect.catchReasons(errorTag, cases, orElse?) — the v4-canonical way to dispatch on a Schema.TaggedErrorClass reason union. … NEVER write manual if (cause.reason instanceof X) ladders inside a catch block." The new code manually checks error.reason._tag === "GitInvocationFailed" inside an Effect.catch block, which is the exact dispatch-on-reason-inside-catch pattern the rule prohibits. The codebase's canonical Effect.catchReasons usage is at packages/core/src/errors.ts:228-241 (restoreLegacyThrow).

Suggested change
Effect.catch((error) =>
error.reason._tag === "GitInvocationFailed"
? Effect.succeed(false)
: Effect.fail(error),
),
Effect.catchReasons(
"ReactDoctorError",
{
GitInvocationFailed: () => Effect.succeed(false),
},
(_reason, error) => Effect.fail(error),
),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

Scan crashes when git is missing or the --diff base ref isn't present (CI shallow clones)

3 participants