Skip to content

fix: shorten refresh backoff for transient failures and log the cause (#178) - #179

Merged
Percy2Live merged 1 commit into
mainfrom
fix/178-refresh-retry-backoff
Sep 27, 2026
Merged

Percy2Live merged 1 commit into
mainfrom
fix/178-refresh-retry-backoff

Conversation

@Percy2Live

Copy link
Copy Markdown
Owner

Closes part of #178.

Problem

A failed OAuth token refresh set a single, fixed one-hour throttle (DEFAULT_REFRESH_RETRY_DELAY_MS) regardless of the cause. Combined with occasional packet loss on the reporter's line, one transient error during a refresh stopped telemetry for up to 75 minutes (60 min throttle + backoff). On short poll intervals this happened several times an hour.

On top of that, the actual reason of a failed refresh was never logged — onFailure only wrote status.lastError, which the next poll's throttle message immediately overwrote, so the original error was invisible.

Change

  • Classify the refresh failure (classifyRefreshFailure, duck-typed on the BluettiOAuthTokenClientError reason/httpStatus to avoid a circular import):
  • Log the underlying refresh error once via a new onRefreshFailure callback, wired to this.log.warn in main.ts. It fires only on real attempts, not on throttled polls, so it does not spam.

Not addressed here (needs reporter input)

Point 1 of the issue — "token refreshed on every poll". The code already stamps created_at from expires_in (normalizeTokenResponse, fix for #46), so expiry should be computable and refresh should not run every poll. If it still does for the reporter, BLUETTI's refresh response likely omits expires_in entirely. I'll ask on the issue which expiry fields the stored token actually contains before guessing at a fallback lifetime.

Tests

Added unit tests: transient failure retries after the short delay; rejected credentials keep the full backoff; the failure is reported exactly once per failure window (not on throttled polls); and the classifier mapping. npm run check, lint, test:ts (99 passing) and test:package (57 passing) all green.

🤖 Generated with Claude Code

…#178)

A failed OAuth token refresh blocked further refreshes for a fixed hour
regardless of cause, so a single transient network error, timeout, or 5xx
could stop telemetry for up to 75 minutes.

Classify the refresh failure: rejected credentials (invalid_grant and other
4xx) keep the hour-long backoff because retrying a dead refresh token cannot
help, while transient failures back off only ~1 minute so the next poll can
retry. Also surface the underlying refresh error once as a warning, since it
was previously only written to status.lastError and immediately overwritten
by the throttle message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Percy2Live
Percy2Live merged commit 447d8d7 into main Sep 27, 2026
12 checks passed
@Percy2Live
Percy2Live deleted the fix/178-refresh-retry-backoff branch September 27, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant