Skip to content

GitHub credential failover + usage-burn: possible bugs from rate-limit investigation (all unverified) #989

Description

@arul28

Investigation from an ADE chat session on 2026-07-31. Everything below is unconfirmed — findings came from reading code and live machine state, not from reproducing failures. Each item needs verification before it is treated as a real bug.

Origin: a machine appeared "rate limited" in Settings. Root cause turned out to be the Anthropic Max 20x 5-hour session window, not GitHub — GitHub was at 4997/5000 core quota and the ADE GitHub App token was valid. Investigating the GitHub side anyway surfaced the credential-failover gaps below.


1. No failover when the selected GitHub credential is rate-limited

Likelihood: high · Needs verification

GitHubRateLimitError is thrown at apps/desktop/src/main/services/github/githubService.ts:1127 but appears to be caught nowhere in apps/desktop/src/main/services (grep across all non-test files found only the definition, the import, and the throw).

If the active credential gets 429'd, ADE surfaces "Rate limited" (apps/desktop/src/renderer/lib/githubIntegrationStatus.ts:284, apps/desktop/src/main/services/prs/prService.ts:8398) and stops. A stored PAT in .ade/secrets/github is never tried as a second leg.

Verify: force a 429 (or stub the fetch) with both a gh token and a stored PAT present; confirm whether any caller recovers.

2. Credential precedence selects first-that-exists, not first-that-works

Likelihood: high · Needs verification

apps/desktop/src/shared/githubOperationCredential.ts:1 fixes the order environment -> gh -> pat, and selectGithubOperationCredential returns on the first resolver that yields a non-null credential — no health check, no retry on failure:

for (const source of GITHUB_OPERATION_CREDENTIAL_PRECEDENCE) {
  const credential = resolvers[source]();
  if (credential) return credential;
}

This likely affects invalid_token (401) the same way it affects rate limiting: a broken gh token should mask a working stored PAT.

Verify: store a valid PAT, deliberately break the gh token, and check whether ADE recovers or reports "invalid token".

3. GitHub App user token is relay-only, so it cannot act as a third leg

Likelihood: medium · Needs verification

githubAppUserToken is resolved at apps/desktop/src/main/services/github/githubService.ts:1460 and consumed only by apps/desktop/src/main/services/github/githubRelayConfig.ts (relay auth). It does not appear in GITHUB_OPERATION_CREDENTIAL_PRECEDENCE, so it is not a candidate for direct API operations.

Verify: confirm this is deliberate (scope/permission reasons) rather than an oversight, and whether an installation token could legitimately serve direct API calls.

4. Possible absence of a per-source cooldown after a 429

Likelihood: medium · Needs verification

Rate-limit headers are parsed correctly, including retry-after and x-ratelimit-resource (apps/desktop/src/main/services/github/githubRateLimit.ts), and retryAt is computed. It is unclear whether anything actually suppresses further requests to a limited source until resetAt — status caching exists, but that is not the same as poll suppression.

Verify: after a 429, confirm whether PR polling keeps hitting the limited credential (which would deepen a secondary rate limit rather than back off).

5. No guard or early warning for concurrent Opus sessions exhausting the Anthropic window

Likelihood: medium · Needs verification (product question as much as a bug)

The originating incident: 2,736 Opus-5 calls in one 5-hour window — 727,059,121 cache-read + 18,106,896 cache-write + 1,439,705 output tokens — from five concurrent Opus chat sessions plus roughly nine subagents underneath them. The account has hasExtraUsageEnabled: false, so exhaustion is a hard stop with no overflow.

UsageQuotaPanel.tsx renders quota state, but it is unverified whether it warned before the limit hit, and whether ADE reflects the provider limit anywhere the user would see it while launching more parallel sessions.

Verify: confirm what the usage panel showed in the lead-up, and decide whether ADE should warn or throttle when several heavy sessions run concurrently.

6. Possible cache-read amplification in long-running sessions

Likelihood: low-medium · Needs verification

727M cache-read across 2,736 calls averages roughly 266K cached tokens re-read per call — consistent with sessions running near a full context window where every tool call re-bills the whole prompt. This may indicate a compaction threshold that lets sessions sit too close to the ceiling for too long.

Verify: measure per-call cached-token growth against the compaction trigger point; determine whether earlier compaction materially reduces the burn.


What is working correctly (no action needed)

  • Rate-limit header parsing, including retry-after and x-ratelimit-resource.
  • Status caching means a 429 does not blank the UI.
  • ETag conditional GETs are cached.
  • User-facing copy is honest and does not wrongly tell users to re-authenticate: "ADE is signed in, but GitHub paused API requests until X. No authentication command is needed."

Suggested direction (not yet agreed)

Make the precedence loop retry the next source on GitHubRateLimitError / 403-abuse instead of committing to the first existing credential, and mark the failed source as cooling down until its resetAt. That alone would give every user a real second leg (gh -> PAT); adding the App installation token would make three.

No code was changed during this investigation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions