fix: Rate-limit-resilient usage polling + silent token refresh
The /api/oauth/usage endpoint enforces a tight long-window per-account quota (observed Retry-After values up to 36 minutes, see anthropics/claude-code#31637). The previous polling pattern - every saved account fetched back-to-back within ~100ms, on every refresh, with no backoff - tripped it constantly: in a real 3-account setup, 56% of all usage requests (1113 of 1998) came back 429, and the UI showed 'API Rate Limited' or 'Token expired' most of the time. Polling changes: - Detect 429 as a typed error carrying the Retry-After header value. - Park a rate-limited account until the server-given deadline (floor 60s, cap 1h) instead of hammering it; keep the last known usage sample so the UI shows stale percentages, not an error banner. - Retry once, politely (>=3s), only when Retry-After is short (<=30s); long deadlines rethrow and park. - Round-robin: each cycle fetches the active account plus ONE non-active account instead of all of them, with a 1.5s stagger between the two requests (bursts reliably got all but one 429'd). - Re-entrancy guard on refresh() so overlapping refreshes (timer + manual + post-switch) cannot double-burst. Token refresh: - Non-active backup credentials now refresh in place via a direct POST to the OAuth token endpoint (Claude Code's public PKCE client id) when they expire - no keychain swap, so no race with running Claude Code sessions. Access tokens only live ~8 hours, so without this every non-active account sat in a permanent 'Token expired' state between switches. A rejected refresh grant (dead/rotated refresh token) still surfaces as 'Session expired. Re-authenticate'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
S
Sandor Bogyo committed
72065eb7fe204befbef9cd84b392faf1c3269962
Parent: 1f3b9c8