Skip to content

Refresh button is a no-op while a provider is in backoff #5

Open

Description

@danielbanariba

Problem

The Refresh button in the KDE popup cannot recover a provider that is in backoff, because it goes through the same guard the poll timer does. Once a provider has failed enough times to reach the 15-minute backoff cap, clicking Refresh does nothing at all — no request is made, no error is shown, and the card keeps displaying stale numbers.

shared/core/scheduler.js:

function queueProviderRequest(name, provider) {
    const state = providerStates.get(name);
    if (!state || !provider || typeof provider.getUsage !== 'function')
        return Promise.resolve();

    if (backoffManager.shouldBackoff(name))
        return Promise.resolve();        // <-- no way past this
    ...
}

async function refresh() {
    const runs = providers.map(({name, provider}) => queueProviderRequest(name, provider));
    await Promise.all(runs);
    return getSummary();
}

refresh() is the only entry point, and kde/plasma6/contents/ui/main.qml calls it from both places:

  • line 82 — Timer { interval: App.DEFAULT_POLL_INTERVAL_MS; onTriggered: root.pollNow() }
  • line 162 — Button { text: i18n("Refresh"); onClicked: root.pollNow() }

So a manual refresh is indistinguishable from an automatic tick. There is no force path.

Why this is worse than it sounds: the UI keeps looking live

Two labels in the popup are computed locally, so they keep moving while the data behind them is frozen:

  • "Next update in 3m" counts down off the poll timer, which keeps firing — the requests are just dropped.
  • "Resets in 4h 24m" is derived from the stored resets_at ISO string, so it keeps ticking against the wall clock.

The card therefore looks like it is updating. There is no visual indication that a provider is in backoff.

Reproduction

  1. Put the Claude provider into repeated failure. The easiest natural trigger is an expired OAuth access token: ~/.claude/.credentials.json has an expiresAt in the past and the refresh call also fails (offline, or a transient 5xx). Each failure calls backoffManager.scheduleBackoff, doubling from 30s toward the 15-minute cap.
  2. Let the failures continue for a few minutes so attempt climbs.
  3. Restore connectivity / a valid token.
  4. Open the popup and click Refresh.

Expected: an explicit user action issues a request immediately, regardless of backoff state.

Actual: queueProviderRequest returns Promise.resolve() before touching the network. The panel and the card keep the previous values, and "Next update in 3m" keeps counting down as if a fetch were pending.

Observed gap

Panel and popup versus a direct call to the same endpoint (api.anthropic.com/api/oauth/usage), read within the same second:

widget endpoint
Claude session 90% left 45% left
Claude weekly 29% left 25% left

A 45-point gap on the session window. The 5-hour window had rolled over a few minutes before the widget's last successful fetch, so it captured a nearly-empty window and then froze there while usage climbed. Restarting plasmashell — which clears the in-memory backoff state — brought the panel to 24% against an endpoint reading of 25%, i.e. current.

Suggested fix

Add an opt-in bypass rather than weakening the automatic backoff, which is doing its job:

function queueProviderRequest(name, provider, {force = false} = {}) {
    ...
    if (!force && backoffManager.shouldBackoff(name))
        return Promise.resolve();
    ...
}

async function refresh({force = false} = {}) {
    const runs = providers.map(({name, provider}) => queueProviderRequest(name, provider, {force}));
    await Promise.all(runs);
    return getSummary();
}

and have the Refresh button pass {force: true} while the timer keeps calling refresh() unchanged. A manual click is a strong signal that the user wants a request now; the automatic cadence should still be protected.

I have a patch for this and will open a PR.

Environment

  • brainusage 1.0.1 (kde/plasma6)
  • KDE Plasma 6 on Wayland, Arch-based distribution
  • Claude provider only; a second provider on the same install is unauthenticated and fails independently, which does not affect the Claude card since provider states are keyed by name

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions