Skip to content

loopover-mcp login's device-flow polling gets 429'd by its own /v1/auth/* strict rate limit #6792

Description

@JSONbored

Problem

loopover-mcp login's device-flow path (loginWithDeviceFlow(), packages/loopover-mcp/bin/loopover-mcp.js:3806-3819) is effectively unusable as deployed: it polls POST /v1/auth/github/device/poll on the interval GitHub itself returns (defaults to every 5s), but that route falls under routeClassForPath's blanket path.startsWith("/v1/auth/") return "strict" (src/auth/rate-limit.ts:125), and strict allows only 10 requests per 60 seconds (src/auth/rate-limit.ts:22). A 5-second poll interval alone burns that budget in ~50 seconds even with zero other /v1/auth/* traffic — well within GitHub's own normal device-flow completion time for a human (reading the code, switching to a browser tab, logging in if needed, clicking authorize).

Reproduction

Found while verifying AMS's standalone-repo usage end-to-end (part of the verification work for the AMS-standalone-usage docs pass). Ran a real, source-built @loopover/mcp login against production https://api.loopover.ai from a real client machine, 4 separate times across several minutes (including after explicit cooldowns past the reported resetAt):

Open https://github.com/login/device and enter code F666-176E.
{"ok": false, "error": "LoopOver API 429 retry-after=4s: {\"error\":\"rate_limited\",\"routeClass\":\"strict\",\"retryAfterSeconds\":4,...}"}

Open https://github.com/login/device and enter code 3809-B321.
{"ok": false, "error": "LoopOver API 429 retry-after=7s: {\"error\":\"rate_limited\",\"routeClass\":\"strict\",\"retryAfterSeconds\":7,...}"}

Open https://github.com/login/device and enter code E758-B1E7.
{"ok": false, "error": "LoopOver API 429 retry-after=7s: {\"error\":\"rate_limited\",\"routeClass\":\"strict\",\"retryAfterSeconds\":7,...}"}

Open https://github.com/login/device and enter code 7F06-6F14.
{"ok": false, "error": "LoopOver API 429 retry-after=7s: {\"error\":\"rate_limited\",\"routeClass\":\"strict\",\"retryAfterSeconds\":7,\"resetAt\":\"2026-07-17T06:51:51.172Z\"}"}

Every attempt failed within seconds of login starting — well before a human could plausibly complete the device-flow authorization even on the first attempt, and this got worse, not better, across successive attempts (each login call also hits /v1/auth/github/device/start, which shares the /v1/auth/* prefix match and presumably contends for a related budget). No zombie/background processes from prior attempts were left running (checked ps aux before each retry) — this is not self-inflicted by leaked polling loops, just the combination of /device/start + a normal /device/poll cadence against a 10-req/60s ceiling.

Root cause

routeClassForPath (src/auth/rate-limit.ts:112-126) applies strict to every path under /v1/auth/, uniformly. That's the right posture for one-shot auth actions (a login attempt, a token exchange, a webhook receiver) where 10/60s is a reasonable anti-abuse ceiling. It's the wrong posture for /v1/auth/github/device/poll specifically, which is designed by the OAuth Device Authorization Grant spec (RFC 8628) to be polled repeatedly at a server-specified interval for up to the code's full expiry window (this repo's own start.expiresIn defaults to 900s / 15 minutes, packages/loopover-mcp/bin/loopover-mcp.js:3809) — the whole point of the interval/slow_down handshake in the spec is that repeated polling is expected, not abuse.

The same route also propagates the failure ungracefully: apiFetch throwing on a non-2xx response means a single transient 429 aborts the entire loginWithDeviceFlow() attempt (packages/loopover-mcp/bin/loopover-mcp.js:3811-3818's while loop has no catch around the apiFetch poll call), rather than treating a 429 the same way it already treats GitHub's own slow_down status (back off and keep polling within the deadline).

Suggested scope for a fix (not investigated further here — this issue is the bug report, not the fix)

Two independent, likely-both-warranted angles:

  1. Give /v1/auth/github/device/poll (and possibly /device/start) its own rate-limit class/budget sized for the device-flow spec's actual cadence (e.g. normal at 120/60s, or a bespoke class), instead of inheriting the blanket /v1/auth/*strict mapping.
  2. Make loginWithDeviceFlow()'s poll loop resilient to a transient 429 from its own backend the same way it already handles GitHub's slow_down — back off using the returned retry-after/retryAfterSeconds and keep polling until the deadline, instead of throwing out of the whole attempt.

Why this matters beyond AMS

This is the general-purpose loopover-mcp login command, not something AMS-specific — any contributor or self-hoster using the documented device-flow login path hits this. It also affects @loopover/miner's own init --interactive → "Authorize with GitHub" option if it's ever pointed at an app whose device-flow polling routes through this same middleware (not applicable to the currently-configured LOOPOVER_MINER_AMS_OAUTH_CLIENT_ID target, which polls github.com directly and is unaffected by this repo's own rate limiter — but worth keeping in mind if that ever changes).

Note: this blocked part of a verification pass

This bug blocked completing one deliverable of a broader AMS standalone-usage verification pass (confirming resolveGitHubToken's live-session-fetch path, packages/loopover-miner/lib/github-token-resolution.js, end-to-end) — that verification could not be completed because loopover-mcp login itself couldn't reach a successful session. The plain-GITHUB_TOKEN path was verified working end-to-end separately.

Metadata

Metadata

Assignees

Labels

gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions