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:
- 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.
- 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.
Problem
loopover-mcp login's device-flow path (loginWithDeviceFlow(),packages/loopover-mcp/bin/loopover-mcp.js:3806-3819) is effectively unusable as deployed: it pollsPOST /v1/auth/github/device/pollon the interval GitHub itself returns (defaults to every 5s), but that route falls underrouteClassForPath's blanketpath.startsWith("/v1/auth/") return "strict"(src/auth/rate-limit.ts:125), andstrictallows 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 loginagainst productionhttps://api.loopover.aifrom a real client machine, 4 separate times across several minutes (including after explicit cooldowns past the reportedresetAt):Every attempt failed within seconds of
loginstarting — 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 (eachlogincall 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 (checkedps auxbefore each retry) — this is not self-inflicted by leaked polling loops, just the combination of/device/start+ a normal/device/pollcadence against a 10-req/60s ceiling.Root cause
routeClassForPath(src/auth/rate-limit.ts:112-126) appliesstrictto 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/pollspecifically, 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 ownstart.expiresIndefaults to 900s / 15 minutes,packages/loopover-mcp/bin/loopover-mcp.js:3809) — the whole point of theinterval/slow_downhandshake in the spec is that repeated polling is expected, not abuse.The same route also propagates the failure ungracefully:
apiFetchthrowing on a non-2xx response means a single transient 429 aborts the entireloginWithDeviceFlow()attempt (packages/loopover-mcp/bin/loopover-mcp.js:3811-3818'swhileloop has no catch around theapiFetchpoll call), rather than treating a 429 the same way it already treats GitHub's ownslow_downstatus (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:
/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.normalat 120/60s, or a bespoke class), instead of inheriting the blanket/v1/auth/*→strictmapping.loginWithDeviceFlow()'s poll loop resilient to a transient 429 from its own backend the same way it already handles GitHub'sslow_down— back off using the returnedretry-after/retryAfterSecondsand 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 logincommand, not something AMS-specific — any contributor or self-hoster using the documented device-flow login path hits this. It also affects@loopover/miner's owninit --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-configuredLOOPOVER_MINER_AMS_OAUTH_CLIENT_IDtarget, which pollsgithub.comdirectly 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 becauseloopover-mcp loginitself couldn't reach a successful session. The plain-GITHUB_TOKENpath was verified working end-to-end separately.