diff --git a/.changeset/fn-8650-grok-cli-auth-expired.md b/.changeset/fn-8650-grok-cli-auth-expired.md
new file mode 100644
index 0000000000..430defdd31
--- /dev/null
+++ b/.changeset/fn-8650-grok-cli-auth-expired.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Stop reporting a working Grok CLI login as expired auth in the Usage indicator.
+category: fix
+dev: Grok billing fetches now preserve auth, no-data, HTTP, and transport outcomes.
diff --git a/packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx b/packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx
index 3f30547c63..e9867ef106 100644
--- a/packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx
+++ b/packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx
@@ -185,6 +185,22 @@ describe("UsageIndicator", () => {
expect(screen.getByText("Hourly")).toBeInTheDocument();
});
+ it("renders an authenticated provider with no windows without an error badge or meter", () => {
+ mockUseUsageData.mockReturnValue(createUsageDataState({
+ providers: [{ name: "Grok", icon: "✖️", status: "ok", windows: [] }],
+ lastUpdated: new Date(),
+ refresh: mockRefresh,
+ }));
+
+ render();
+
+ const card = document.querySelector('[data-provider="Grok"]')!;
+ expect(card).toHaveAttribute("data-status", "ok");
+ expect(card.querySelector(".usage-status-badge--error")).toBeNull();
+ expect(card.querySelector(".usage-provider-windows")).toBeNull();
+ expect(screen.getByText("No usage data available")).toBeInTheDocument();
+ });
+
it("renders drag handle in the right-side actions cluster for each provider card", () => {
mockUseUsageData.mockReturnValue(createUsageDataState({
providers: mockProviders,
diff --git a/packages/dashboard/src/__tests__/usage.test.ts b/packages/dashboard/src/__tests__/usage.test.ts
index 846449c368..d480e2eb1e 100644
--- a/packages/dashboard/src/__tests__/usage.test.ts
+++ b/packages/dashboard/src/__tests__/usage.test.ts
@@ -3819,8 +3819,7 @@ describe("usage", () => {
expect(mockRequest).toHaveBeenCalledTimes(1);
});
- it("does not fabricate a weekly credit window when the CLI billing percentage is omitted", async () => {
- vi.stubEnv("GROK_API_KEY", "env-grok-key");
+ it("keeps a healthy unmeterable CLI login authenticated when the billing percentage is omitted", async () => {
mockReadFile.mockImplementation(async (filePath: string) => {
if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
return Promise.reject(new Error("File not found"));
@@ -3846,7 +3845,8 @@ describe("usage", () => {
expect(grok.status).toBe("ok");
expect(grok.windows).toEqual([]);
expect(grok.windows.some((window) => window.percentUsed === 100)).toBe(false);
- expect(mockRequest).toHaveBeenCalledTimes(2);
+ expect(grok.error ?? "").not.toMatch(/auth expired/i);
+ expect(mockRequest).toHaveBeenCalledTimes(1);
});
it("renders a zero-percent weekly CLI credit window", async () => {
@@ -3886,9 +3886,28 @@ describe("usage", () => {
const providers = await fetchAllProviderUsage();
const grok = providers.find((provider) => provider.name === "Grok")!;
- expect(grok.status).toBe("error");
+ expect(grok.status).toBe("ok");
expect(grok.windows).toEqual([]);
- expect(grok.error).toContain("grok login");
+ expect(grok.error ?? "").not.toMatch(/auth expired/i);
+ });
+
+ it.each([
+ ["missing config", {}],
+ ["invalid config", { config: null }],
+ ["empty body", ""],
+ ])("keeps a CLI login authenticated for %s billing data", async (_description, body) => {
+ mockReadFile.mockImplementation(async (filePath: string) => {
+ if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
+ return Promise.reject(new Error("File not found"));
+ });
+ mockGrokBillingResponse(200, body);
+
+ const providers = await fetchAllProviderUsage();
+ const grok = providers.find((provider) => provider.name === "Grok")!;
+
+ expect(grok.status).toBe("ok");
+ expect(grok.windows).toEqual([]);
+ expect(grok.error ?? "").not.toMatch(/auth expired/i);
});
it("retains the Credits label for a non-weekly numeric CLI percentage", async () => {
@@ -3914,6 +3933,56 @@ describe("usage", () => {
});
});
+ it.each([401, 403])("shows CLI auth expiry only for observed billing HTTP %s without an API key", async (statusCode) => {
+ mockReadFile.mockImplementation(async (filePath: string) => {
+ if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
+ return Promise.reject(new Error("File not found"));
+ });
+ mockGrokBillingResponse(statusCode, { error: "unauthorized" });
+
+ const providers = await fetchAllProviderUsage();
+ const grok = providers.find((provider) => provider.name === "Grok")!;
+
+ expect(grok.status).toBe("error");
+ expect(grok.error).toMatch(/auth expired/i);
+ });
+
+ it.each([429, 500])("reports billing HTTP %s without claiming CLI auth expired", async (statusCode) => {
+ mockReadFile.mockImplementation(async (filePath: string) => {
+ if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
+ return Promise.reject(new Error("File not found"));
+ });
+ mockGrokBillingResponse(statusCode, { error: "unavailable" });
+
+ const providers = await fetchAllProviderUsage();
+ const grok = providers.find((provider) => provider.name === "Grok")!;
+
+ expect(grok.status).toBe("error");
+ expect(grok.error).toContain(`HTTP ${statusCode}`);
+ expect(grok.error ?? "").not.toMatch(/auth expired/i);
+ });
+
+ it("reports a CLI billing transport failure without claiming auth expired", async () => {
+ mockReadFile.mockImplementation(async (filePath: string) => {
+ if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
+ return Promise.reject(new Error("File not found"));
+ });
+ mockRequest.mockImplementation(() => ({
+ on: vi.fn((event: string, handler: (error: Error) => void) => {
+ if (event === "error") handler(new Error("network unavailable"));
+ }),
+ write: vi.fn(),
+ end: vi.fn(),
+ }));
+
+ const providers = await fetchAllProviderUsage();
+ const grok = providers.find((provider) => provider.name === "Grok")!;
+
+ expect(grok.status).toBe("error");
+ expect(grok.error).toContain("network unavailable");
+ expect(grok.error ?? "").not.toMatch(/auth expired/i);
+ });
+
it("falls back to the xAI API-key validity card when CLI billing fails", async () => {
vi.stubEnv("GROK_API_KEY", "env-grok-key");
mockReadFile.mockImplementation(async (filePath: string) => {
diff --git a/packages/dashboard/src/usage.ts b/packages/dashboard/src/usage.ts
index 456f774945..9ba864f263 100644
--- a/packages/dashboard/src/usage.ts
+++ b/packages/dashboard/src/usage.ts
@@ -1694,12 +1694,22 @@ async function readGrokCliOidcToken(): Promise {
return null;
}
+type GrokCliBillingUsageOutcome =
+ | { outcome: "window" }
+ | { outcome: "unauthorized" }
+ | { outcome: "no-data" }
+ | { outcome: "http-error"; status: number }
+ | { outcome: "transport-error"; message: string };
+
/**
* Fetch Grok subscription credit usage via the grok CLI's billing endpoint.
- * Returns null when the request fails in any way so the caller can fall back
- * to the xAI API-key auth-validity card.
+ * Preserves the observed reason no usage window was emitted so callers never
+ * infer authentication failure from unavailable meter data.
*/
-async function fetchGrokCliBillingUsage(token: string, usage: ProviderUsage): Promise {
+async function fetchGrokCliBillingUsage(
+ token: string,
+ usage: ProviderUsage,
+): Promise {
try {
const res = await httpsRequest("https://cli-chat-proxy.grok.com/v1/billing?format=credits", {
method: "GET",
@@ -1708,21 +1718,22 @@ async function fetchGrokCliBillingUsage(token: string, usage: ProviderUsage): Pr
"content-type": "application/json",
},
});
- if (res.status !== 200) return false;
+ if (res.status === 401 || res.status === 403) return { outcome: "unauthorized" };
+ if (res.status !== 200) return { outcome: "http-error", status: res.status };
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- untyped API response
const data: any = res.body.trim().length > 0 ? JSON.parse(res.body) : {};
const config = data?.config;
- if (!config || typeof config !== "object") return false;
+ if (!config || typeof config !== "object") return { outcome: "no-data" };
const parsedReset = _parseResetTimestamp(config.billingPeriodEnd ?? config.currentPeriod?.end);
const isWeekly = config.currentPeriod?.type === "USAGE_PERIOD_TYPE_WEEKLY";
/*
FNXC:UsageProviders 2026-07-31-20:31:
- A real account reported zero Grok credit usage while its billing response omitted `creditUsagePercent`, disproving the former omitted-field-to-100% inference. Emit a credits window only for a finite API-supplied percentage; field absence must fall through to the API-key validity or CLI-auth error card rather than fabricate consumption.
+ A real account reported zero Grok credit usage while its billing response omitted `creditUsagePercent`, disproving the former omitted-field-to-100% inference. Emit a credits window only for a finite API-supplied percentage; field absence must remain an authenticated but unmeterable state rather than fabricate consumption or infer expired CLI auth.
*/
const pctUsed = config.creditUsagePercent;
- if (typeof pctUsed !== "number" || !Number.isFinite(pctUsed)) return false;
+ if (typeof pctUsed !== "number" || !Number.isFinite(pctUsed)) return { outcome: "no-data" };
usage.windows.push({
label: isWeekly ? "Weekly (credits)" : "Credits",
@@ -1734,9 +1745,12 @@ async function fetchGrokCliBillingUsage(token: string, usage: ProviderUsage): Pr
windowDurationMs: isWeekly ? 7 * 24 * 60 * 60 * 1000 : undefined,
});
usage.status = "ok";
- return true;
- } catch {
- return false;
+ return { outcome: "window" };
+ } catch (error: unknown) {
+ return {
+ outcome: "transport-error",
+ message: error instanceof Error ? error.message : "Failed to fetch",
+ };
}
}
@@ -1765,17 +1779,30 @@ async function fetchGrokUsage(authStorage?: AuthStorageLike): Promise