Skip to content

Sticky rateLimited flag permanently locks the app after a single 403 (even after adding a PAT) #480

@ionfwsrijan

Description

@ionfwsrijan

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

  1. Use the app without a PAT (or with a low remaining quota).
  2. Trigger enough requests to eventually get a 403 from GitHub.
  3. Observe the error: “GitHub API rate limit exceeded. Please provide a PAT to continue.”
  4. Add a PAT (so getOctokit() would return an authenticated client).
  5. 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)

  1. Replace boolean rateLimited with a small state machine, e.g. ok | cooldown | blocked, plus cooldownUntil.
  2. Allow manual retry even during cooldown (or allow retry when auth changes), while throttling background refresh.
  3. When PAT/token changes (or getOctokit() becomes authenticated), clear the cooldown/blocked state and retry automatically or enable retry.
  4. Use GitHub headers (x-ratelimit-reset, retry-after) to drive UX (“Try again in N seconds”) instead of permanently blocking.
  5. 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

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions