Skip to content

fix: show status badges in overview when no quota data is available#489

Open
RaghavShubham wants to merge 2 commits into
robinebers:mainfrom
RaghavShubham:fix/status-badge-hidden-in-overview
Open

fix: show status badges in overview when no quota data is available#489
RaghavShubham wants to merge 2 commits into
robinebers:mainfrom
RaghavShubham:fix/status-badge-hidden-in-overview

Conversation

@RaghavShubham
Copy link
Copy Markdown

@RaghavShubham RaghavShubham commented May 22, 2026

Problem

When a plugin returns only a status badge — "No usage data" or "Rate limited" — and the card is rendered in overview/menu-bar scope, the badge was silently dropped, leaving the card visibly blank.

Root cause

ProviderCard filters lines by scope:

const filteredLines = scopeFilter === "all"
  ? lines
  : lines.filter(line => overviewLabels.has(line.label))

overviewLabels is built from skeletonLines with scope === "overview". For Claude that's only "Session" and "Weekly". The fallback badge has label "Status" — not in the set — so it gets filtered out.

After the first successful probe sets lastUpdatedAt, hasStaleData becomes true, but filteredLines stays empty. The {hasStaleData && …} block renders with no children → blank card.

This affects Enterprise accounts (API responds but contains no recognized quota fields), inference-only tokens, and any provider where the plugin falls back to a status-only badge.

Fix

src/components/provider-card.tsx — Badge-type lines are status indicators, not metric lines. They must always pass the scope filter:

// Badge lines are status indicators (e.g. "No usage data", "Rate limited") and must
// always be shown regardless of scope so the overview card is never silently blank.
const filteredLines = scopeFilter === "all"
  ? lines
  : lines.filter(line => line.type === "badge" || overviewLabels.has(line.label))

plugins/claude/plugin.js — Improve the fallback message so users can distinguish a working connection from an auth/network failure:

  • API connected, response has no recognized quota fields → "Connected — no quota data" (e.g. Enterprise plans)
  • Inference-only token / no API call made → "No usage data" (unchanged)

Tests

  • Added regression test in provider-card.test.tsx that reproduces the exact scenario: overview scope + lastUpdatedAt set + only a "Status" badge → badge must be visible.
  • Updated plugin.test.js to match the new "Connected — no quota data" text for the API-connected case, and added a complementary test for the inference-only path (must still show "No usage data" and must not call the HTTP API).

All 1087 tests pass.

Closes #440


Summary by cubic

Always show status badges in the overview and clarify Claude’s status when personal quota data isn’t available, including Enterprise org‑billing accounts.

  • Bug Fixes
    • ProviderCard always shows badge-type lines in overview so cards don’t render blank.
    • Claude plugin sets a clear status badge per scenario: 403 from usage API → "Enterprise — org-level billing"; API reachable but no recognized fields → "Connected — no quota data"; inference-only tokens → "No usage data" and skips HTTP calls.

Written for commit 4813319. Summary will update on new commits. Review in cubic

When a plugin returns only a "Status" badge (e.g. "No usage data" or
"Rate limited") and the card is rendered in overview scope, the badge
was silently filtered out because its label was not listed as an
overview-scoped line in plugin.json.  This left the card visibly
blank after the first successful probe set `lastUpdatedAt`, making
`hasStaleData` true while `filteredLines` remained empty.

Fix the scope filter in ProviderCard to always pass badge-type lines
through regardless of scope — they are status indicators, not metric
lines, and should always be visible.

Also improve the Claude plugin's fallback message when the usage API
responds successfully but returns no recognized quota fields (e.g.
Enterprise plans or future plan types): show "Connected — no quota
data" instead of the generic "No usage data" so users can distinguish
a working connection from an authentication or network failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Re-trigger cubic

The personal usage endpoint (/api/oauth/usage) returns 403 for Enterprise
accounts because their billing is tracked at the organisation level, not
per-user.  Previously the plugin treated any 403 as a token-expired error,
which caused the JS probe to throw, the Rust runtime to emit an Error badge,
and the state management to set data=null.  On the first load this left the
provider card completely blank because hasStaleData was false and no
PluginError was prominently surfaced.

Fix: intercept 403 from the usage endpoint before the generic isAuthStatus
check and treat it as "no personal quota data" instead.  The probe no longer
throws, returns a "Status: Enterprise — org-level billing" badge, and the
card renders meaningful content on the first load.

A three-way fallback is now emitted when lines is empty:
  • 403 response                      → "Enterprise — org-level billing"
  • 200 but no recognized quota fields → "Connected — no quota data"
  • Inference-only / no API call       → "No usage data"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="plugins/claude/plugin.js">

<violation number="1" location="plugins/claude/plugin.js:632">
P2: Enterprise/org-billing status badge is not stable across probes because `orgBillingOnly` is function-local and lost when requests are throttled via the min-interval guard.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread plugins/claude/plugin.js
let lines = []
let rateLimited = false
let retryAfterSeconds = null
let orgBillingOnly = false // true when the API returned 403 (Enterprise org-level billing)
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Enterprise/org-billing status badge is not stable across probes because orgBillingOnly is function-local and lost when requests are throttled via the min-interval guard.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/claude/plugin.js, line 632:

<comment>Enterprise/org-billing status badge is not stable across probes because `orgBillingOnly` is function-local and lost when requests are throttled via the min-interval guard.</comment>

<file context>
@@ -629,6 +629,7 @@
     let lines = []
     let rateLimited = false
     let retryAfterSeconds = null
+    let orgBillingOnly = false  // true when the API returned 403 (Enterprise org-level billing)
     if (canFetchLiveUsage) {
       if (nowMs < rateLimitedUntilMs) {
</file context>
Fix with Cubic

@validatedev validatedev requested a review from Copilot May 23, 2026 21:57
@validatedev
Copy link
Copy Markdown
Collaborator

@codex review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an overview rendering bug where providers that only emit a status badge (e.g. “No usage data”, “Rate limited”) could appear blank in overview/menu-bar scope, and it refines the Claude plugin’s fallback status messaging when quota data can’t be derived.

Changes:

  • Update ProviderCard scope filtering so status-only output isn’t silently dropped in overview scope.
  • Improve Claude plugin behavior/messages for “connected but no quota fields”, and for usage API 403 (Enterprise org-billing).
  • Add/adjust regression tests covering the overview badge scenario and Claude fallback paths.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/components/provider-card.tsx Adjusts runtime line filtering logic in overview scope to avoid blank cards.
src/components/provider-card.test.tsx Adds a regression test ensuring a status badge renders in overview scope.
plugins/claude/plugin.js Adds clearer fallback badges for “no quota fields” and Enterprise (403) scenarios.
plugins/claude/plugin.test.js Updates/adds tests to validate the new Claude status badge behaviors and API-call skipping.
Comments suppressed due to low confidence (1)

plugins/claude/plugin.js:639

  • orgBillingOnly is a per-probe local flag. If the usage request is skipped on subsequent probes (min-interval throttle or existing rate-limit window), the code won’t remember that the last real response was 403 and may fall back to the wrong status badge. Consider deriving this state from the cached value (or caching a sentinel) so the badge remains accurate across skipped polls.
    let lines = []
    let rateLimited = false
    let retryAfterSeconds = null
    let orgBillingOnly = false  // true when the API returned 403 (Enterprise org-level billing)
    if (canFetchLiveUsage) {
      if (nowMs < rateLimitedUntilMs) {
        // Still within a rate-limit window from a previous probe call — skip the
        // API request entirely and surface the remaining wait time to the user.
        rateLimited = true
        retryAfterSeconds = Math.ceil((rateLimitedUntilMs - nowMs) / 1000)
        data = cachedUsageData

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +128 to 133
// Badge lines are status indicators (e.g. "No usage data", "Rate limited") and must
// always be shown regardless of scope so the overview card is never silently blank.
const filteredLines = scopeFilter === "all"
? lines
: lines.filter(line => overviewLabels.has(line.label))
: lines.filter(line => line.type === "badge" || overviewLabels.has(line.label))

Comment thread plugins/claude/plugin.js
Comment on lines +696 to +706
if (resp.status === 403) {
// A 403 from the usage endpoint means the account type does not have access
// to personal quota data. Enterprise accounts are billed at the organisation
// level and consistently return 403 here. Treat it as "no personal quota data"
// so the card shows a helpful badge rather than the error state, which leaves
// it blank on first load. A 401 (truly expired token) falls through to the
// isAuthStatus handler below.
ctx.host.log.info("usage API returned 403 — organisation-level billing; no personal quota data")
orgBillingOnly = true
data = cachedUsageData // keep previous cache if any, otherwise null
} else if (ctx.util.isAuthStatus(resp.status)) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude provider not displaying quota remaining

3 participants