Skip to content

Fix misleading private repository message during partial fetch#301

Closed
jagriti-aswal wants to merge 2 commits into
GitMetricsLab:mainfrom
jagriti-aswal:fix-misleading-private-repo-error
Closed

Fix misleading private repository message during partial fetch#301
jagriti-aswal wants to merge 2 commits into
GitMetricsLab:mainfrom
jagriti-aswal:fix-misleading-private-repo-error

Conversation

@jagriti-aswal
Copy link
Copy Markdown
Contributor

@jagriti-aswal jagriti-aswal commented May 17, 2026

Related Issue

Closes #300


Description

This PR improves GitHub tracker error handling during partial API fetch failures.

Changes made:

  • Replaced Promise.all() with Promise.allSettled()
  • Allowed partial GitHub data rendering when one request succeeds
  • Prevented misleading global "Private repository detected" errors during partial fetches
  • Added contextual warning messages for incomplete data fetch scenarios
  • Improved overall async error handling behavior

How Has This Been Tested?

  • Tested with valid GitHub usernames and invalid/random PATs
  • Verified pull request data still renders during partial fetch failures
  • Confirmed misleading private repository message no longer appears during partial success cases

Screenshots

Before
Screenshot 2026-05-17 201423
After
Screenshot 2026-05-17 201901

Added in PR discussion.


Type Of Change

  • Bug fix
  • New feature
  • Code style update

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved GitHub search reliability by allowing partial results when one data source experiences issues
    • Enhanced error messages for common GitHub API failures including rate limiting and permission errors

Review Change Stack

@netlify
Copy link
Copy Markdown

netlify Bot commented May 17, 2026

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit 45887e4
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/6a09d7f1b72ee8000837e0dc
😎 Deploy Preview https://deploy-preview-301--github-spy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

useGitHubData replaces Promise.all with Promise.allSettled to fetch GitHub issues and pull requests concurrently, enabling partial success. State updates now apply independently per request, and error handling is restructured to distinguish validation failures from permission errors.

Changes

Partial Success Fetch and Error Handling

Layer / File(s) Summary
Promise.allSettled with independent state updates and error classification
src/hooks/useGitHubData.ts
fetchData switches from Promise.all to Promise.allSettled, separately updates issues/totalIssues and prs/totalPrs only for fulfilled requests, sets "could not be fetched completely" error when either request rejects, and restructures the catch block's conditional chain to reorder and rewrite error-to-message mappings (validation, permission/private repo, 401, 404).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • GitMetricsLab/github_tracker#238: Both PRs modify src/hooks/useGitHubData.ts—reworking fetchData/error handling logic including permission and private repository error message mapping.
  • GitMetricsLab/github_tracker#149: Both PRs modify useGitHubData's fetchData logic to fetch issues and PRs concurrently with independent state management.

Poem

🐰 Two requests now hop side by side,
No need to wait or fear to collide—
Each partial success finds its place,
Errors less misleading, kinder grace.
Promise.allSettled saves the day!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix misleading private repository message during partial fetch' directly and specifically addresses the main change—preventing a misleading error from appearing when partial GitHub data loads successfully.
Description check ✅ Passed The description is complete and well-structured, covering related issue, detailed changes, testing approach, and evidence with before/after screenshots aligned with the template.
Linked Issues check ✅ Passed The PR directly addresses all objectives from issue #300: replaced Promise.all() with Promise.allSettled() for partial success [#300], separated error states for different API fetches [#300], and prevents misleading private repo errors during partial data loads [#300].
Out of Scope Changes check ✅ Passed All code changes in useGitHubData.ts are directly scoped to resolving issue #300's objectives—error handling restructuring, async handling improvements, and partial fetch support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useGitHubData.ts (1)

62-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove or refactor unreachable error handling code.

With Promise.allSettled, this catch block is now mostly unreachable. Promise.allSettled always resolves (never throws), so the specific error handling for 403 rate limiting (lines 64-66), user not found (lines 67-68), validation failures (lines 70-72), permission errors (lines 73-74), and 404 errors (lines 75-77) will never execute under normal circumstances.

The only way this catch block executes now is if:

  • getOctokit() throws synchronously (before the Promise.allSettled)
  • There's a code error in the try block itself

This makes the newly added validation error handling (lines 70-72) and reordered permission/404 logic (lines 73-77) ineffective.

Either:

  1. Remove this dead code after moving specific error handling to inspect rejection reasons (as suggested in the previous comment), OR
  2. Keep only the generic fallback handler for unexpected synchronous errors
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGitHubData.ts` around lines 62 - 83, The catch block in
useGitHubData is handling specific API errors even though the fetches use
Promise.allSettled (which never throws), so those branches (403/rate limit, "do
not exist", "validation failed", 401/permission, 404) are effectively dead;
either remove them and keep only a generic fallback for synchronous failures
(e.g., errors thrown by getOctokit or other runtime exceptions), or move the
specific logic into the Promise.allSettled result inspection (check each
settledResult.reason or value to setRateLimited, setError('User not found'),
handle validation/permission/404 cases) so error-specific messages are derived
from the settled promises rather than this catch; update useGitHubData
accordingly and keep getOctokit-related synchronous error handling in the catch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useGitHubData.ts`:
- Around line 39-59: In useGitHubData, Promise.allSettled now swallows throw
behavior so inspect the settled results' reason fields (issueRes.reason and
prRes.reason) to restore specific handling: if either reason.status === 403 call
rateLimited(true) and set an appropriate PAT prompt, if either is
401/404/validation use those codes to set precise error messages, and if both
are rejected treat it as a complete failure (setError to indicate total failure)
vs. a partial failure when only one rejected; update the logic around
setIssues/setPrs, setTotalIssues/setTotalPrs accordingly so fulfilled results
are used while rejected ones are examined for their .reason to decide
rateLimited(), specific error text, or generic error.

---

Outside diff comments:
In `@src/hooks/useGitHubData.ts`:
- Around line 62-83: The catch block in useGitHubData is handling specific API
errors even though the fetches use Promise.allSettled (which never throws), so
those branches (403/rate limit, "do not exist", "validation failed",
401/permission, 404) are effectively dead; either remove them and keep only a
generic fallback for synchronous failures (e.g., errors thrown by getOctokit or
other runtime exceptions), or move the specific logic into the
Promise.allSettled result inspection (check each settledResult.reason or value
to setRateLimited, setError('User not found'), handle validation/permission/404
cases) so error-specific messages are derived from the settled promises rather
than this catch; update useGitHubData accordingly and keep getOctokit-related
synchronous error handling in the catch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02b64201-2464-4d71-82e2-9643eb92ca84

📥 Commits

Reviewing files that changed from the base of the PR and between 8d17610 and 45887e4.

📒 Files selected for processing (1)
  • src/hooks/useGitHubData.ts

Comment on lines +39 to +59
const [issueRes, prRes] = await Promise.allSettled([
fetchPaginated(octokit, username, 'issue', page, perPage),
fetchPaginated(octokit, username, 'pr', page, perPage),
]);

setIssues(issueRes.items);
setPrs(prRes.items);
setTotalIssues(issueRes.total);
setTotalPrs(prRes.total);
if (issueRes.status === 'fulfilled') {
setIssues(issueRes.value.items);
setTotalIssues(issueRes.value.total);
}

if (prRes.status === 'fulfilled') {
setPrs(prRes.value.items);
setTotalPrs(prRes.value.total);
}

if (
issueRes.status === 'rejected' ||
prRes.status === 'rejected'
) {
setError('Some GitHub data could not be fetched completely.');
}
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Inspect rejection reasons to preserve specific error handling and fix rate limiting detection.

The switch to Promise.allSettled prevents specific error details from reaching the catch block. Critical issues:

  1. Rate limiting detection is broken: Line 61 always sets rateLimited(false), but the catch block's 403 handling (lines 64-66) is now unreachable since Promise.allSettled never throws. Users hitting rate limits won't be prompted to add a PAT.

  2. Misleading error when both requests fail: When both issueRes and prRes are rejected, "Some GitHub data could not be fetched completely" incorrectly implies partial success. It should indicate complete failure.

  3. Loss of specific error context: Rejected promises contain error details in their reason property (403 rate limit, 401 permission, 404 not found, validation errors), but these aren't inspected.

🔧 Proposed fix to inspect rejection reasons
 const [issueRes, prRes] = await Promise.allSettled([
   fetchPaginated(octokit, username, 'issue', page, perPage),
   fetchPaginated(octokit, username, 'pr', page, perPage),
 ]);

+// Check for rate limiting or critical errors in rejections
+const rejections = [issueRes, prRes].filter(r => r.status === 'rejected');
+for (const rejection of rejections) {
+  const err: any = rejection.reason;
+  if (err.status === 403) {
+    setError('GitHub API rate limit exceeded. Please provide a PAT to continue.');
+    setRateLimited(true);
+    setLoading(false);
+    return;
+  }
+}
+
 if (issueRes.status === 'fulfilled') {
   setIssues(issueRes.value.items);
   setTotalIssues(issueRes.value.total);
 }

 if (prRes.status === 'fulfilled') {
   setPrs(prRes.value.items);
   setTotalPrs(prRes.value.total);
 }

-if (
-  issueRes.status === 'rejected' ||
-  prRes.status === 'rejected'
-) {
-  setError('Some GitHub data could not be fetched completely.');
+// Set appropriate error message based on success/failure mix
+const bothFailed = issueRes.status === 'rejected' && prRes.status === 'rejected';
+const partialFailure = issueRes.status === 'rejected' || prRes.status === 'rejected';
+
+if (bothFailed) {
+  // Extract specific error message from one of the rejections
+  const err: any = issueRes.reason || prRes.reason;
+  const errorMessage = err.message?.toLowerCase() || "";
+  
+  if (errorMessage.includes("do not exist")) {
+    setError('User not found. Please check the spelling of the GitHub username.');
+  } else if (errorMessage.includes("validation failed")) {
+    setError('Invalid GitHub username or insufficient permissions.');
+  } else if (err.status === 401 || errorMessage.includes("permission")) {
+    setError('Private repository detected. Please input PAT.');
+  } else if (err.status === 404) {
+    setError('Resource not found.');
+  } else {
+    setError('Unable to fetch GitHub data. Please verify the username, token, or network connection.');
+  }
+} else if (partialFailure) {
+  setError('Some GitHub data could not be fetched completely.');
 }

 setRateLimited(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [issueRes, prRes] = await Promise.allSettled([
fetchPaginated(octokit, username, 'issue', page, perPage),
fetchPaginated(octokit, username, 'pr', page, perPage),
]);
setIssues(issueRes.items);
setPrs(prRes.items);
setTotalIssues(issueRes.total);
setTotalPrs(prRes.total);
if (issueRes.status === 'fulfilled') {
setIssues(issueRes.value.items);
setTotalIssues(issueRes.value.total);
}
if (prRes.status === 'fulfilled') {
setPrs(prRes.value.items);
setTotalPrs(prRes.value.total);
}
if (
issueRes.status === 'rejected' ||
prRes.status === 'rejected'
) {
setError('Some GitHub data could not be fetched completely.');
}
const [issueRes, prRes] = await Promise.allSettled([
fetchPaginated(octokit, username, 'issue', page, perPage),
fetchPaginated(octokit, username, 'pr', page, perPage),
]);
// Check for rate limiting or critical errors in rejections
const rejections = [issueRes, prRes].filter(r => r.status === 'rejected');
for (const rejection of rejections) {
const err: any = rejection.reason;
if (err.status === 403) {
setError('GitHub API rate limit exceeded. Please provide a PAT to continue.');
setRateLimited(true);
setLoading(false);
return;
}
}
if (issueRes.status === 'fulfilled') {
setIssues(issueRes.value.items);
setTotalIssues(issueRes.value.total);
}
if (prRes.status === 'fulfilled') {
setPrs(prRes.value.items);
setTotalPrs(prRes.value.total);
}
// Set appropriate error message based on success/failure mix
const bothFailed = issueRes.status === 'rejected' && prRes.status === 'rejected';
const partialFailure = issueRes.status === 'rejected' || prRes.status === 'rejected';
if (bothFailed) {
// Extract specific error message from one of the rejections
const err: any = issueRes.reason || prRes.reason;
const errorMessage = err.message?.toLowerCase() || "";
if (errorMessage.includes("do not exist")) {
setError('User not found. Please check the spelling of the GitHub username.');
} else if (errorMessage.includes("validation failed")) {
setError('Invalid GitHub username or insufficient permissions.');
} else if (err.status === 401 || errorMessage.includes("permission")) {
setError('Private repository detected. Please input PAT.');
} else if (err.status === 404) {
setError('Resource not found.');
} else {
setError('Unable to fetch GitHub data. Please verify the username, token, or network connection.');
}
} else if (partialFailure) {
setError('Some GitHub data could not be fetched completely.');
}
setRateLimited(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGitHubData.ts` around lines 39 - 59, In useGitHubData,
Promise.allSettled now swallows throw behavior so inspect the settled results'
reason fields (issueRes.reason and prRes.reason) to restore specific handling:
if either reason.status === 403 call rateLimited(true) and set an appropriate
PAT prompt, if either is 401/404/validation use those codes to set precise error
messages, and if both are rejected treat it as a complete failure (setError to
indicate total failure) vs. a partial failure when only one rejected; update the
logic around setIssues/setPrs, setTotalIssues/setTotalPrs accordingly so
fulfilled results are used while rejected ones are examined for their .reason to
decide rateLimited(), specific error text, or generic error.

@jagriti-aswal
Copy link
Copy Markdown
Contributor Author

After further debugging, I found that the displayed PR data was coming from stale frontend state rather than a successful partial GitHub fetch. The issue is therefore different from the originally reported behavior.

I identified the actual problem as stale state persistence and improved fetch/error handling accordingly. I’ll create a separate issue and PR focused on the correct root cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect "Private repository detected" message shown despite successful GitHub data fetch

1 participant