From 783a07bfb73a138e74c53910ff5520e851cfde2c Mon Sep 17 00:00:00 2001 From: Lukas May Date: Tue, 10 Feb 2026 13:16:03 +0100 Subject: [PATCH] fix: Show actionable error details for account health check failures Setup tokens from `claude setup-token` can't query the usage API, resulting in a useless "Usage API request failed" message. Now shows the actual HTTP status and guides users to complete OAuth setup. Also distinguishes warning state (yellow) from error state (red) in the AccountCard UI. --- packages/web/src/components/AccountCard.tsx | 14 ++++-- src/agent/accounts/usage.ts | 54 ++++++++++++++++----- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/packages/web/src/components/AccountCard.tsx b/packages/web/src/components/AccountCard.tsx index 4270098..3e68351 100644 --- a/packages/web/src/components/AccountCard.tsx +++ b/packages/web/src/components/AccountCard.tsx @@ -93,10 +93,14 @@ export type AccountData = { }; export function AccountCard({ account }: { account: AccountData }) { + const hasWarning = account.credentialsValid && !account.isExhausted && account.error; + const statusIcon = !account.credentialsValid ? ( ) : account.isExhausted ? ( + ) : hasWarning ? ( + ) : ( ); @@ -105,7 +109,9 @@ export function AccountCard({ account }: { account: AccountData }) { ? "Invalid credentials" : account.isExhausted ? `Exhausted until ${account.exhaustedUntil ? new Date(account.exhaustedUntil).toLocaleTimeString() : "unknown"}` - : "Available"; + : hasWarning + ? "Setup incomplete" + : "Available"; const usage = account.usage; @@ -189,9 +195,11 @@ export function AccountCard({ account }: { account: AccountData }) { )} - {/* Error message */} + {/* Error / warning message */} {account.error && ( -

{account.error}

+

+ {account.error} +

)} diff --git a/src/agent/accounts/usage.ts b/src/agent/accounts/usage.ts index 0fc2a7f..d75c761 100644 --- a/src/agent/accounts/usage.ts +++ b/src/agent/accounts/usage.ts @@ -149,7 +149,12 @@ async function refreshToken( } } -async function fetchUsage(accessToken: string): Promise { +type FetchUsageResult = + | { ok: true; usage: AccountUsage } + | { ok: false; status: number; statusText: string } + | { ok: false; status: 0; statusText: string }; + +async function fetchUsage(accessToken: string): Promise { try { const response = await fetch(USAGE_API_URL, { method: 'GET', @@ -159,17 +164,22 @@ async function fetchUsage(accessToken: string): Promise { 'Content-Type': 'application/json', }, }); - if (!response.ok) return null; + if (!response.ok) { + return { ok: false, status: response.status, statusText: response.statusText }; + } const data = await response.json(); return { - five_hour: data.five_hour ?? null, - seven_day: data.seven_day ?? null, - seven_day_sonnet: data.seven_day_sonnet ?? null, - seven_day_opus: data.seven_day_opus ?? null, - extra_usage: data.extra_usage ?? null, + ok: true, + usage: { + five_hour: data.five_hour ?? null, + seven_day: data.seven_day ?? null, + seven_day_sonnet: data.seven_day_sonnet ?? null, + seven_day_opus: data.seven_day_opus ?? null, + extra_usage: data.extra_usage ?? null, + }, }; - } catch { - return null; + } catch (err) { + return { ok: false, status: 0, statusText: err instanceof Error ? err.message : 'Network error' }; } } @@ -280,12 +290,30 @@ export async function checkAccountHealth( } } - const usage = await fetchUsage(accessToken); - if (!usage) { + const isSetupToken = !currentExpiresAt; + const usageResult = await fetchUsage(accessToken); + + if (!usageResult.ok) { + const statusDetail = usageResult.status > 0 + ? `HTTP ${usageResult.status} ${usageResult.statusText}` + : usageResult.statusText; + + if (isSetupToken) { + // Setup tokens often can't query the usage API — not a hard error + return { + ...base, + credentialsValid: true, + tokenValid: true, + tokenExpiresAt: null, + subscriptionType, + error: `Usage API unavailable for setup token (${statusDetail}). Run \`claude\` with this account to complete OAuth setup.`, + }; + } + return { ...base, credentialsValid: true, - error: 'Usage API request failed', + error: `Usage API request failed: ${statusDetail}`, }; } @@ -295,7 +323,7 @@ export async function checkAccountHealth( tokenValid: true, tokenExpiresAt: currentExpiresAt ? new Date(currentExpiresAt).toISOString() : null, subscriptionType, - usage, + usage: usageResult.usage, }; } catch (err) { return {