Repository navigation
Poll the usage endpoint every 5 minutes and back off on 429 - #7
Conversation
The /api/oauth/usage endpoint allows only a handful of requests per window, and that budget is shared with Claude Code's own /usage screen. Polling it every 60s (with a 30s server cache) tripped the limit and made /usage unusable until the cooldown passed. - Client re-fetches every 5 minutes; countdown text re-renders every minute without a network call. - Server caches for 5 minutes, coalesces concurrent callers into one request, honors Retry-After on 429, and otherwise backs off 5/10/20/40/60 minutes. Credential errors stay unthrottled. - While backing off, the last good reading is served and shown as stale with the reason and its age. - README documents the rate-limit behavior. Fixes #6 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
No objective defects were identified, and the rate-limit fix's new caching and backoff logic is well-scoped and thoroughly covered by the added tests.
No open issues 🎉
What changed in this PR
This PR fixes issue #6, where the plugin polled the rate‑limited https://api.anthropic.com/api/oauth/usage endpoint every 60s (with only a 30s cache), causing HTTP 429 errors that made /usage unusable. It introduces a server‑side LiveCache that enforces a 5‑minute polling cadence, coalesces concurrent callers, and backs off on 429, while the client slows network polling to 5 minutes and keeps a lightweight 1‑minute tick for re‑rendering countdown/"updated" text. The UI now keeps showing the last good reading (marked stale) during backoff instead of blanking the gauges.
Changes:
- New
LiveCacheinsrc/live.tswith 5‑minute caching, request coalescing,Retry-After‑aware 429 handling, and exponential backoff (5 → 10 → 20 → 40 → 60 min) that resets on success. - Server refactored to check credentials locally (unthrottled) before serving the cache; client re‑fetches every 5 min and re‑renders every minute without network calls, surfacing stale readings in the UI.
- 13 new tests in
src/live-cache.test.tsplus README documentation of the new cadence and rate‑limit behavior.
| File | Description |
|---|---|
src/live.ts |
Adds LiveCache, backoffMs, parseRetryAfter, LIVE_CACHE_MS, and the UsageResponse/LiveResult types that implement caching and 429 backoff. |
src/server.ts |
Extracts readCredentials() as an unthrottled local check and wires /live through the new LiveCache instead of the ad‑hoc 30s cache. |
src/index.ts |
Splits polling (5 min network) from rendering (1 min tick), extends LiveData with stale/error/retry fields, and renders the stale "last reading" note and "updated Xm ago" text. |
src/live-cache.test.ts |
New unit tests covering cache window, coalescing, 429 with/without Retry-After, backoff reset, stale serving, and non‑429 error handling. |
README.md |
Documents the 5‑minute cadence and the rate‑limiting/backoff behavior. |
I reviewed the caching and backoff logic in detail (concurrency coalescing in get(), the result() null-safety invariant, backoff reset on success, and parseRetryAfter edge cases), verified there are no dangling references to the removed 30s cache path, and confirmed the new behavior matches the tests and README. I did not find objective defects worth flagging — the change is well-scoped and the new logic is thoroughly covered by tests.
Review effort: Balanced
Fixes #6
Problem
The plugin polled
https://api.anthropic.com/api/oauth/usageevery 60s with only a 30s server cache, so every poll reached the endpoint. That endpoint allows only a handful of requests per window, and the budget is shared with Claude Code's own/usagescreen and statusline (see anthropics/claude-code#30930 and anthropics/claude-code#31637). Users hit HTTP 429 and/usagebecame unusable until the cooldown passed. A 429 was also never cached or backed off, so the next poll retried a minute later.Fix
LiveCache(src/live.ts) calls the endpoint at most once every 5 minutes, coalesces concurrent callers into one request, honorsRetry-Afteron 429, and otherwise backs off 5 → 10 → 20 → 40 → 60 minutes. A success resets the backoff. Non-429 failures retry at the normal cadence. Credential errors (missing or expired token) are checked locally first and stay unthrottled, so a fresh login shows up immediately.Testing
src/live-cache.test.ts(written first, watched fail) cover the cache window, request coalescing, 429 with and withoutRetry-After, exponential backoff with reset on success, stale serving, and throttling after non-429 errors. Full suite: 33 tests passing.GET /livehit the endpoint, second returned from cache with the samefetchedAt.🤖 Generated with Claude Code