Skip to content

fix(profile): differentiate 404 from transient GitHub API failures#188

Closed
Adit-Jain-srm wants to merge 7 commits into
PRODHOSH:mainfrom
Adit-Jain-srm:fix/175-fetchgithubuser-error-handling
Closed

fix(profile): differentiate 404 from transient GitHub API failures#188
Adit-Jain-srm wants to merge 7 commits into
PRODHOSH:mainfrom
Adit-Jain-srm:fix/175-fetchgithubuser-error-handling

Conversation

@Adit-Jain-srm

@Adit-Jain-srm Adit-Jain-srm commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the issue where rate limits (429) and server errors (5xx) from GitHub's API incorrectly show the "profile not found" page. Now only a genuine 404 triggers the not-found page, while transient failures propagate to the error boundary for recovery.

Note: After merging with main, this PR also incorporates the OG/Twitter metadata improvements from #174 (now adapted to use the discriminated union pattern with runtime type guards).

Related Issue

Closes #175

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactor
  • Chore / dependency update

Changes Made

Modified src/app/[username]/page.tsx:

  1. Discriminated union return type for fetchGitHubUser:

    • { status: 'ok', data } for successful responses
    • { status: 'not_found' } for HTTP 404
    • { status: 'error', code } for anything else (429, 500, 503, network failures)
  2. Try-catch on both fetch functions to handle network errors and malformed JSON gracefully

  3. Updated page component to call notFound() only on genuine 404, throw on transient errors (caught by error boundary)

  4. Enhanced generateMetadata (merged from feat(meta): add OG and Twitter Card tags to profile pages #174) now includes:

    • OpenGraph tags (title, description, image from avatar, type: profile, siteName)
    • Twitter Card tags (summary card with avatar)
    • Dynamic description using bio, repo count, and follower count
    • All fields accessed with runtime typeof checks (no unsafe type assertions)
  5. Distinct error messages: network failures (code 0) say "Network error" while HTTP failures show the actual status code

AI Usage

  • I did not use AI for any part of the code in this PR
  • I used AI for coding (Cursor with Claude) and I fully understand every change I made, including which functions I changed, why I changed them, and what side effects they could create

Screenshots (if UI change)

No visible change for the happy path. The difference is only observable during GitHub outages or rate limiting.

Checklist

  • I was assigned to the issue before opening this PR
  • My branch is up to date with main
  • Code works locally and I have tested it
  • No console.log left in src/
  • If schema changed - both schema.sql and a new migration file are included
  • If this is a UI change - I read DESIGN.md and followed the design system (colors, spacing, typography, components)
  • Docs updated if needed
  • PR title follows Conventional Commits format (feat:, fix:, docs:, etc.)
  • This PR description is written in my own words

Summary by CodeRabbit

  • Bug Fixes
    • Improved GitHub profile loading so missing profiles consistently show the “not found” page, while other failures surface clearer error behavior.
    • Made repository loading more resilient so network/unexpected issues no longer break the page and instead show an empty repositories list.
  • Refactor
    • Updated profile metadata generation to reflect success vs. failure, using richer display details when available and conditionally including avatar-based social images.

…loses PRODHOSH#175)

fetchGitHubUser now returns a discriminated union instead of nullable JSON. The page component only calls notFound() on a genuine 404. Rate limits and server errors throw, which the error boundary catches with a retry option. generateMetadata falls back gracefully on any failure.

Signed-off-by: Adit Jain <aditjain2005@gmail.com>
@github-actions github-actions Bot added the frontend Related to UI / Next.js label Jun 23, 2026
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

fetchGitHubUser now returns structured success, not-found, and error results instead of null. fetchGitHubRepos now handles thrown failures. generateMetadata and ProfilePage both branch explicitly on the new result states.

Changes

Structured fetch result and consumer updates

Layer / File(s) Summary
UserFetchResult type and fetch functions
src/app/[username]/page.tsx
Introduces the UserFetchResult discriminated union and rewrites fetchGitHubUser to return ok, not_found, or error results instead of null. fetchGitHubRepos is wrapped in try/catch to return an empty array on thrown errors while preserving fork filtering and the six-repo limit.
Metadata and page control flow
src/app/[username]/page.tsx
generateMetadata returns fallback username-only metadata for non-ok results and derives name, bio, counts, and avatar image metadata for ok. ProfilePage uses Promise.all, calls notFound() only for not_found, throws an Error for error, and renders from userResult.data for ok.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Possibly related PRs

  • PRODHOSH/ossfolio#174: Both PRs update generateMetadata in src/app/[username]/page.tsx to build GitHub-derived metadata from fetched user data with avatar-based image tags.
  • PRODHOSH/ossfolio#188: This PR further develops the same profile-page fetch flow by adding explicit not_found and error handling for the GitHub user request.

Suggested reviewers

  • PRODHOSH

Poem

🐇 I hopped through GitHub’s misty glow,
With ok and error set in a row.
No more blank null in the night —
Now pages know what’s wrong or right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: separating 404s from transient GitHub API failures in profile handling.
Linked Issues check ✅ Passed The PR meets #175 by distinguishing ok/not_found/error, keeping 404 as notFound and routing 429/5xx/network failures away from notFound.
Out of Scope Changes check ✅ Passed All changes stay within the profile page flow and related metadata/type handling, with no unrelated scope introduced.

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

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

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

@Adit-Jain-srm

Copy link
Copy Markdown
Contributor Author

Could you add the elusoc label? Issue #175 has ELUSOC + ADVENTURER already. Thanks!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/app/`[username]/page.tsx:
- Around line 28-35: The fetchGitHubUser function does not handle exceptions
that can be thrown by fetch() (network errors, timeouts) or res.json()
(malformed JSON parsing), which causes these errors to propagate uncaught and
bypass the discriminated union error handling. Wrap the entire function body in
a try-catch block, keeping the existing status checks and return statements in
the try block, and add a catch block that returns { status: "error" } to handle
any thrown exceptions consistently with the error handling pattern.
- Around line 51-66: The generateMetadata function handles the case where
fetchGitHubUser returns a non-ok status but does not handle exceptions that may
be thrown by the fetchGitHubUser call itself (such as network errors or JSON
parsing errors). Wrap the fetchGitHubUser call in a try-catch block so that any
thrown exception is caught and the fallback metadata object (with title and
description using the username) is returned in the catch block, ensuring
metadata generation does not crash when fetchGitHubUser throws.
- Line 61: Remove the unsafe type assertion `(user.name as string)` on the
displayName variable assignment. Instead of asserting the type, use a safer
pattern that actually validates the type at runtime, such as checking whether
user.name is a string using typeof or a type guard before assigning it to
displayName. This ensures type safety and provides clear intent that the code is
checking for a string value rather than bypassing type checking with an
assertion.
- Around line 72-78: Add a try-catch error handling block to the
fetchGitHubRepos function to ensure consistency with other fetch functions like
fetchLiveStats, fetchOrganizations, and fetchContributionCalendar. Wrap the
entire function body (including the fetch call and res.json() parsing on line
47) in a try block, and add a catch block that returns an empty array. This
prevents promise rejections from invalid JSON responses and ensures all fetch
functions in the Promise.all statement handle errors consistently.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 88ecdad7-a816-4b03-88a3-ab1e980fbf15

📥 Commits

Reviewing files that changed from the base of the PR and between e26793a and 4869236.

📒 Files selected for processing (1)
  • src/app/[username]/page.tsx

Comment thread src/app/[username]/page.tsx Outdated
Comment thread src/app/[username]/page.tsx
Comment thread src/app/[username]/page.tsx Outdated
Comment thread src/app/[username]/page.tsx
… assertion

Addresses CodeRabbit review:
- fetchGitHubUser: wrapped in try-catch so network errors return { status: 'error' } instead of throwing
- fetchGitHubRepos: wrapped in try-catch for consistency with other fetch functions
- Replaced (user.name as string) with typeof runtime check
- Skipped generateMetadata try-catch: redundant since fetchGitHubUser now never throws

Signed-off-by: Adit Jain <aditjain2005@gmail.com>
…ags)

Merges the discriminated union error handling from this PR with the OG/Twitter metadata from PR PRODHOSH#174 (now on main). Uses typeof runtime checks to safely access user fields from Record<string, unknown>.

Signed-off-by: Adit Jain <aditjain2005@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
src/app/[username]/page.tsx (2)

111-113: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify error message for network/parse failures.

When userResult.code is 0 (from the catch block in fetchGitHubUser line 38), the error message "GitHub API returned 0" is misleading. A code of 0 indicates a network error or JSON parse failure, not an HTTP status code.

📝 Suggested improvement for error message clarity
   if (userResult.status === "error") {
-    throw new Error(`GitHub API returned ${userResult.code} for ${username}`);
+    const errorMsg = userResult.code === 0
+      ? `Failed to fetch GitHub user ${username} (network or parse error)`
+      : `GitHub API returned ${userResult.code} for ${username}`;
+    throw new Error(errorMsg);
   }
🤖 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/app/`[username]/page.tsx around lines 111 - 113, The error message in the
condition checking if userResult.status is "error" treats userResult.code of 0
as an HTTP status code, but 0 actually indicates a network error or JSON parse
failure. Modify the error message logic to check if userResult.code equals 0 and
provide a clarified error message indicating a network or parsing issue, while
keeping the current HTTP status code message for non-zero codes.

23-26: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Type the discriminated union data field properly for type safety.

The data field is typed as Record<string, unknown>, which disables TypeScript's type checking. When userResult.data is assigned to user (line 114) and passed to ProfileView (line 163), TypeScript cannot verify the shape matches the expected GitHubUser interface.

This forces defensive runtime type guards throughout the code (lines 69-73) and defeats the purpose of using TypeScript. If the GitHub API returns an unexpected shape, the error won't be caught until runtime.

🔒 Recommended fix to add proper typing

First, extract or define the GitHubUser interface in this file or a shared types file:

+interface GitHubUser {
+  login: string;
+  name: string | null;
+  bio: string | null;
+  avatar_url: string;
+  html_url: string;
+  blog: string | null;
+  location: string | null;
+  twitter_username: string | null;
+  followers: number;
+  following: number;
+  public_repos: number;
+}
+
 type UserFetchResult =
-  | { status: "ok"; data: Record<string, unknown> }
+  | { status: "ok"; data: GitHubUser }
   | { status: "not_found" }
   | { status: "error"; code: number };

This change will enable compile-time type checking and make most runtime guards in generateMetadata unnecessary (though keeping them for defense-in-depth is still reasonable).

🤖 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/app/`[username]/page.tsx around lines 23 - 26, The UserFetchResult type
definition uses Record<string, unknown> for the data field, which prevents
TypeScript from validating that the fetched data matches the expected GitHubUser
interface. Replace the Record<string, unknown> type in the "ok" status variant
with the proper GitHubUser interface type. This will enable compile-time type
checking when userResult.data is assigned to user and passed to ProfileView,
making the runtime type guards in the code unnecessary and catching API shape
mismatches at compile time rather than runtime.
🤖 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/app/`[username]/page.tsx:
- Around line 65-94: Update the PR objectives or description to document the
metadata content changes that were implemented in the metadata generation for
the user profile page. Specifically, add notes about the enhanced description
logic (which now includes bio with repo and follower counts when available) and
the addition of OpenGraph and Twitter card metadata properties (including title,
description, images, type, and siteName fields). This ensures reviewers
understand that the PR scope includes not just error handling refactoring but
also these metadata improvements for better SEO and social media sharing.

---

Outside diff comments:
In `@src/app/`[username]/page.tsx:
- Around line 111-113: The error message in the condition checking if
userResult.status is "error" treats userResult.code of 0 as an HTTP status code,
but 0 actually indicates a network error or JSON parse failure. Modify the error
message logic to check if userResult.code equals 0 and provide a clarified error
message indicating a network or parsing issue, while keeping the current HTTP
status code message for non-zero codes.
- Around line 23-26: The UserFetchResult type definition uses Record<string,
unknown> for the data field, which prevents TypeScript from validating that the
fetched data matches the expected GitHubUser interface. Replace the
Record<string, unknown> type in the "ok" status variant with the proper
GitHubUser interface type. This will enable compile-time type checking when
userResult.data is assigned to user and passed to ProfileView, making the
runtime type guards in the code unnecessary and catching API shape mismatches at
compile time rather than runtime.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 97bd6c02-f924-423a-955a-c3060a62f196

📥 Commits

Reviewing files that changed from the base of the PR and between 5a8965e and e21e5aa.

📒 Files selected for processing (1)
  • src/app/[username]/page.tsx

Comment thread src/app/[username]/page.tsx
Signed-off-by: Adit Jain <aditjain2005@gmail.com>
@PRODHOSH

Copy link
Copy Markdown
Owner

@Adit-Jain-srm fix build error
https://github.com/PRODHOSH/ossfolio/actions/runs/28056165164/job/83058936744?pr=188

@PRODHOSH PRODHOSH self-requested a review June 24, 2026 06:30
@PRODHOSH PRODHOSH added ELUSOC ELUSOC project submission in-progress ADVENTURER Intermediate — 25 pts labels Jun 24, 2026
Replaced Record<string, unknown> with a proper interface matching the GitHub REST API response. Removes redundant typeof guards in generateMetadata. Fixes tsc --noEmit failure where ProfileView expected typed GitHubUser.

Signed-off-by: Adit Jain <aditjain2005@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/app/[username]/page.tsx (1)

117-123: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fetch secondary profile data only after the user lookup succeeds.

This Promise.all starts four extra remote calls before you know whether fetchGitHubUser is "ok". For "not_found" and "error" cases, Line 125 and Line 126 still wait on those calls before returning the 404 or throwing, which adds avoidable latency and burns GitHub quota on requests that can never render.

Suggested restructuring
-  const [userResult, repos, liveStats, orgs, contributionCalendar] = await Promise.all([
-    fetchGitHubUser(username),
-    fetchGitHubRepos(username),
-    fetchLiveStats(username),
-    fetchOrganizations(username),
-    fetchContributionCalendar(username),
-  ]);
+  const userResult = await fetchGitHubUser(username);
 
   if (userResult.status === "not_found") notFound();
   if (userResult.status === "error") {
     const msg = userResult.code === 0
       ? `Network error while fetching profile for ${username}`
       : `GitHub API returned ${userResult.code} for ${username}`;
     throw new Error(msg);
   }
   const user = userResult.data;
+
+  const [repos, liveStats, orgs, contributionCalendar] = await Promise.all([
+    fetchGitHubRepos(username),
+    fetchLiveStats(username),
+    fetchOrganizations(username),
+    fetchContributionCalendar(username),
+  ]);
🤖 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/app/`[username]/page.tsx around lines 117 - 123, Fetch the primary user
data first in the page component that calls fetchGitHubUser, then only start
fetchGitHubRepos, fetchLiveStats, fetchOrganizations, and
fetchContributionCalendar after confirming the user result is "ok". Replace the
current Promise.all fan-out with a two-step flow so the not_found and error
paths can return or throw immediately without waiting on secondary requests,
while still keeping the same data shape for the successful render path.
🤖 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.

Outside diff comments:
In `@src/app/`[username]/page.tsx:
- Around line 117-123: Fetch the primary user data first in the page component
that calls fetchGitHubUser, then only start fetchGitHubRepos, fetchLiveStats,
fetchOrganizations, and fetchContributionCalendar after confirming the user
result is "ok". Replace the current Promise.all fan-out with a two-step flow so
the not_found and error paths can return or throw immediately without waiting on
secondary requests, while still keeping the same data shape for the successful
render path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 737492cc-d1c6-462f-b25c-3f7f7139f217

📥 Commits

Reviewing files that changed from the base of the PR and between 468e013 and c7c93c3.

📒 Files selected for processing (1)
  • src/app/[username]/page.tsx

@Adit-Jain-srm

Copy link
Copy Markdown
Contributor Author

All CodeRabbit suggestions addressed, CI is green (build + type-check + CodeQL all passing). The one outside-diff suggestion (sequential fetch instead of Promise.all) was intentionally skipped - it would add ~200ms latency to every page load just to save 4 lightweight calls on the rare 404 case. The parallel fan-out is the correct trade-off for a server-rendered page.

@PRODHOSH this is ready for review whenever you get a chance.

@PRODHOSH

Copy link
Copy Markdown
Owner

@Adit-Jain-srm Please resolve the merge conflicts
Once done, I will merge this pr

Thanks!

…0 repos, rateLimited prop)

Combines our discriminated union error handling with main's new additions:
- OG/Twitter images now auto-injected by opengraph-image.tsx (removed explicit avatar URLs)
- Repos fetch uses per_page=100 (from main)
- Added rateLimited prop to ProfileView (from main's rate limit handling)
- Kept parallel Promise.all (better latency than main's sequential approach)
- Rate limit (429) shows rateLimited state instead of throwing

Signed-off-by: Adit Jain <aditjain2005@gmail.com>
@Adit-Jain-srm

Adit-Jain-srm commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Merge conflicts resolved. Combined our discriminated union approach with main's recent additions (opengraph-image auto-injection, 100 repos, rateLimited prop). Ready for review. @PRODHOSH

All error/rate-limit cases now throw (caught by error boundary with retry). This guarantees user is GitHubUserData (non-null) when passed to ProfileView, fixing the TS2322 type-check failure.

Signed-off-by: Adit Jain <aditjain2005@gmail.com>
@PRODHOSH

Copy link
Copy Markdown
Owner

merge conflicts still exist
pls fix @Adit-Jain-srm

Thanks

@Adit-Jain-srm

Copy link
Copy Markdown
Contributor Author

Closing - main already implements rate-limit-aware error handling with a different pattern (throw-based detection + rateLimited flag). The core issue (#175) is effectively resolved by the current main code which no longer calls notFound() on rate limits. Thanks for the guidance on this one!

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

Labels

ADVENTURER Intermediate — 25 pts ELUSOC ELUSOC project submission fix-merge-conflict frontend Related to UI / Next.js in-progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: fetchGitHubUser conflates 404 with rate limits and server errors

2 participants