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.
This commit is contained in:
@@ -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 ? (
|
||||
<XCircle className="h-5 w-5 shrink-0 text-destructive" />
|
||||
) : account.isExhausted ? (
|
||||
<AlertTriangle className="h-5 w-5 shrink-0 text-yellow-500" />
|
||||
) : hasWarning ? (
|
||||
<AlertTriangle className="h-5 w-5 shrink-0 text-yellow-500" />
|
||||
) : (
|
||||
<CheckCircle2 className="h-5 w-5 shrink-0 text-green-500" />
|
||||
);
|
||||
@@ -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 }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error message */}
|
||||
{/* Error / warning message */}
|
||||
{account.error && (
|
||||
<p className="pl-8 text-xs text-destructive">{account.error}</p>
|
||||
<p className={`pl-8 text-xs ${hasWarning ? 'text-yellow-600 dark:text-yellow-500' : 'text-destructive'}`}>
|
||||
{account.error}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -149,7 +149,12 @@ async function refreshToken(
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUsage(accessToken: string): Promise<AccountUsage | null> {
|
||||
type FetchUsageResult =
|
||||
| { ok: true; usage: AccountUsage }
|
||||
| { ok: false; status: number; statusText: string }
|
||||
| { ok: false; status: 0; statusText: string };
|
||||
|
||||
async function fetchUsage(accessToken: string): Promise<FetchUsageResult> {
|
||||
try {
|
||||
const response = await fetch(USAGE_API_URL, {
|
||||
method: 'GET',
|
||||
@@ -159,17 +164,22 @@ async function fetchUsage(accessToken: string): Promise<AccountUsage | null> {
|
||||
'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 {
|
||||
|
||||
Reference in New Issue
Block a user