diff --git a/src/app/api/goals/sync/route.ts b/src/app/api/goals/sync/route.ts index 359e39909..ee8ef6257 100644 --- a/src/app/api/goals/sync/route.ts +++ b/src/app/api/goals/sync/route.ts @@ -100,11 +100,19 @@ export async function POST() { } ); + if (ghRes.status === 403 || ghRes.status === 429) { + const resetHeader = ghRes.headers.get("X-RateLimit-Reset"); + const resetAt = resetHeader + ? new Date(Number(resetHeader) * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) + : null; + const message = resetAt + ? `GitHub rate limit reached. Sync will resume at ${resetAt}.` + : "GitHub rate limit reached. Please try again in a few minutes."; + return Response.json({ error: message, rateLimited: true }, { status: 429 }); + } + if (!ghRes.ok) { - return Response.json( - { error: "GitHub API error" }, - { status: 502 } - ); + return Response.json({ error: "GitHub API error" }, { status: 502 }); } const ghData = (await ghRes.json()) as { @@ -168,6 +176,15 @@ export async function POST() { } totalUpdated += prIds.length; + } else if (prRes.status === 403 || prRes.status === 429) { + const resetHeader = prRes.headers.get("X-RateLimit-Reset"); + const resetAt = resetHeader + ? new Date(Number(resetHeader) * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) + : null; + const message = resetAt + ? `GitHub rate limit reached. Sync will resume at ${resetAt}.` + : "GitHub rate limit reached. Please try again in a few minutes."; + return Response.json({ error: message, rateLimited: true }, { status: 429 }); } else { return Response.json({ error: "GitHub API error fetching PRs" }, { status: 502 }); } diff --git a/src/components/GoalTracker.tsx b/src/components/GoalTracker.tsx index 905c6921e..f4ae9a8a6 100644 --- a/src/components/GoalTracker.tsx +++ b/src/components/GoalTracker.tsx @@ -71,7 +71,12 @@ export default function GoalTracker() { } else if (res.status === 502) { msg = "GitHub sync failed: Expired token or missing repo scope."; } - setSyncError(msg); + if (res.status === 429) { + const data = await res.json(); + setSyncError(data.error ?? "GitHub rate limit reached. Please try again later."); + } else { + setSyncError("Failed to sync goals. Please try again."); + } return; } await loadGoals();