Problem
The frontend hook src/hooks/useGitHubData.ts sets a local rateLimited state to true on any 403, then hard-blocks all future fetches via an early return:
if (!octokit || !username.trim() || rateLimited) return;
Because rateLimited is only reset to false inside the “successful fetch” path (setRateLimited(false)), the app can enter a deadlock: once rate-limited, it can no longer make requests to recover, even after the user adds a Personal Access Token (PAT).
This is different from general “rate limit exhaustion” issues (e.g. polling too frequently). This is a state/flow bug: the UI prevents recovery.
Impact (critical)
- A transient GitHub
403 (rate limit / secondary rate limit / abuse detection) can make the app unusable until a full page reload.
- The UI tells users to “provide a PAT to continue”, but after adding a PAT the app may still refuse to fetch because
rateLimited === true.
- Pagination, switching tabs, changing filters, or retrying won’t help because fetch is blocked at the hook boundary.
Steps to reproduce
- Use the app without a PAT (or with a low remaining quota).
- Trigger enough requests to eventually get a
403 from GitHub.
- Observe the error: “GitHub API rate limit exceeded. Please provide a PAT to continue.”
- Add a PAT (so
getOctokit() would return an authenticated client).
- Try to run the search/refresh/pagination action again.
Actual behavior
fetchData() returns early when rateLimited is true, so no requests are made and the app stays stuck.
Expected behavior
- After the user provides a PAT, the app should be able to retry requests and recover without a reload.
- Rate limiting should pause background refresh (if any), but should not permanently block manual retries or auth-based recovery.
Root cause (code-level)
In src/hooks/useGitHubData.ts:
setRateLimited(true) on error.status === 403
rateLimited is used as a hard guard that prevents any subsequent request attempts
- The reset path (
setRateLimited(false)) is only reachable after a successful fetch, which becomes unreachable once the guard triggers
Proposed fix (non-trivial)
- Replace boolean
rateLimited with a small state machine, e.g. ok | cooldown | blocked, plus cooldownUntil.
- Allow manual retry even during cooldown (or allow retry when auth changes), while throttling background refresh.
- When PAT/token changes (or
getOctokit() becomes authenticated), clear the cooldown/blocked state and retry automatically or enable retry.
- Use GitHub headers (
x-ratelimit-reset, retry-after) to drive UX (“Try again in N seconds”) instead of permanently blocking.
- Add tests for
useGitHubData ensuring:
- after a 403, adding a PAT allows a successful retry without reload
- cooldown expiry re-enables fetch
- background refresh is paused, but user-initiated retry can proceed
Problem
The frontend hook
src/hooks/useGitHubData.tssets a localrateLimitedstate totrueon any403, then hard-blocks all future fetches via an early return:if (!octokit || !username.trim() || rateLimited) return;Because
rateLimitedis only reset tofalseinside the “successful fetch” path (setRateLimited(false)), the app can enter a deadlock: once rate-limited, it can no longer make requests to recover, even after the user adds a Personal Access Token (PAT).This is different from general “rate limit exhaustion” issues (e.g. polling too frequently). This is a state/flow bug: the UI prevents recovery.
Impact (critical)
403(rate limit / secondary rate limit / abuse detection) can make the app unusable until a full page reload.rateLimited === true.Steps to reproduce
403from GitHub.getOctokit()would return an authenticated client).Actual behavior
fetchData()returns early whenrateLimitedis true, so no requests are made and the app stays stuck.Expected behavior
Root cause (code-level)
In
src/hooks/useGitHubData.ts:setRateLimited(true)onerror.status === 403rateLimitedis used as a hard guard that prevents any subsequent request attemptssetRateLimited(false)) is only reachable after a successful fetch, which becomes unreachable once the guard triggersProposed fix (non-trivial)
rateLimitedwith a small state machine, e.g.ok | cooldown | blocked, pluscooldownUntil.getOctokit()becomes authenticated), clear the cooldown/blocked state and retry automatically or enable retry.x-ratelimit-reset,retry-after) to drive UX (“Try again in N seconds”) instead of permanently blocking.useGitHubDataensuring: