fix(rate-limits): stop reporting Grok usage as 0% when the API omits the percent

mapWeeklyCredits treated an absent creditUsagePercent as a confirmed protobuf
zero, so accounts whose billing response never reports it showed a confident
0% (status ok) and short-circuited the monthly fallback (#15740). That payload
emits onDemandUsed/prepaidBalance zeros, disproving the "encoder drops zeros"
premise the synthetic zero rested on.

Now: use the reported percent, else compute it from used/monthlyLimit through
one guarded division, else fall back to zero only when the payload emits no
usage scalars at all, else report the usage as unavailable with a reason the
status-bar tooltip and the Grok settings pane both surface.
This commit is contained in:
Neil
2026-08-21 23:10:28 -07:00
parent 2b1254d681
commit bcbb684d53
5 changed files with 221 additions and 21 deletions
+102
View File
@@ -100,6 +100,9 @@ describe('fetchGrokRateLimits', () => {
)
})
// Why: this payload emits NO usage scalars, so the omitted percent really is
// the dropped protobuf zero (#9214/#9219). #15740's payload does emit them —
// keep the two shapes apart.
it('maps an omitted protobuf percentage as zero for a weekly credits period', async () => {
authState.file = freshAuthJson()
netFetchMock.mockResolvedValueOnce(
@@ -125,6 +128,105 @@ describe('fetchGrokRateLimits', () => {
expect(netFetchMock).toHaveBeenCalledTimes(1)
})
// Why: #15740 — an absent creditUsagePercent alongside explicitly-emitted zero
// credit fields means "not reported", never 0%.
it('reports usage as unavailable when the credits view omits the percent but emits explicit zero credit fields', async () => {
authState.file = freshAuthJson()
netFetchMock
.mockResolvedValueOnce(
jsonResponse({
config: {
currentPeriod: {
type: 'USAGE_PERIOD_TYPE_WEEKLY',
start: '2026-08-16T12:54:39.515635+00:00',
end: '2026-08-23T12:54:39.515635+00:00'
},
onDemandCap: { val: 100 },
onDemandUsed: { val: 0 },
isUnifiedBillingUser: true,
prepaidBalance: { val: 0 },
topUpMethod: 'TOP_UP_METHOD_SAVED_PAYMENT_METHOD',
billingPeriodStart: '2026-08-16T12:54:39.515635+00:00',
billingPeriodEnd: '2026-08-23T12:54:39.515635+00:00'
}
})
)
.mockResolvedValueOnce(
jsonResponse({
config: {
monthlyLimit: { val: 0 },
used: { val: 37.5 },
billingPeriodStart: '2026-08-16T12:54:39.515635+00:00',
billingPeriodEnd: '2026-08-23T12:54:39.515635+00:00'
}
})
)
const result = await fetchGrokRateLimits()
expect(result.status).toBe('unavailable')
expect(result.weekly).toBeNull()
expect(result.monthly).toBeUndefined()
expect(result.error).toMatch(/did not report a usage percentage/i)
expect(netFetchMock).toHaveBeenCalledTimes(2)
})
it('computes the weekly percentage from used and limit when the percent is omitted', async () => {
authState.file = freshAuthJson()
netFetchMock.mockResolvedValueOnce(
jsonResponse({
config: {
currentPeriod: {
type: 'USAGE_PERIOD_TYPE_WEEKLY',
start: '2026-08-16T12:54:39.515635+00:00',
end: '2026-08-23T12:54:39.515635+00:00'
},
billingPeriodStart: '2026-08-16T12:54:39.515635+00:00',
billingPeriodEnd: '2026-08-23T12:54:39.515635+00:00',
monthlyLimit: { val: 100 },
used: { val: 25 }
}
})
)
const result = await fetchGrokRateLimits()
expect(result.status).toBe('ok')
expect(result.weekly?.usedPercent).toBe(25)
expect(result.weekly?.windowMinutes).toBe(10_080)
expect(netFetchMock).toHaveBeenCalledTimes(1)
})
it.each([{ val: 0 }, { val: '0' }])(
'does not divide by a zero monthly limit (%o)',
async (monthlyLimit) => {
authState.file = freshAuthJson()
netFetchMock
.mockResolvedValueOnce(jsonResponse({ config: { isUnifiedBillingUser: true } }))
.mockResolvedValueOnce(jsonResponse({ config: { monthlyLimit, used: { val: 12 } } }))
const result = await fetchGrokRateLimits()
expect(result.status).toBe('unavailable')
expect(result.weekly).toBeNull()
expect(result.monthly).toBeUndefined()
expect(result.error).toMatch(/did not report a usage percentage/i)
}
)
it('reads a flat billing payload that carries usage fields but no percent', async () => {
authState.file = freshAuthJson()
netFetchMock.mockResolvedValueOnce(
jsonResponse({
monthlyLimit: { val: 200 },
used: { val: 50 },
billingPeriodEnd: '2026-09-01T00:00:00+00:00'
})
)
const result = await fetchGrokRateLimits()
expect(result.status).toBe('ok')
expect(result.weekly?.usedPercent).toBe(25)
expect(netFetchMock).toHaveBeenCalledTimes(1)
})
it('returns unavailable when not signed in even if a token-less auth file exists', async () => {
authState.file = JSON.stringify({})
const result = await fetchGrokRateLimits()
+75 -18
View File
@@ -89,8 +89,9 @@ function timestampsMatch(left: string | undefined, right: string | undefined): b
function hasConfirmedWeeklyPeriod(config: GrokBillingConfig): boolean {
const period = config.currentPeriod
// Why: monthly unified-billing responses can also carry a weekly currentPeriod;
// matching billing bounds identify Grok's omitted protobuf zero unambiguously.
// Why: matching billing bounds only prove the current period IS the billing
// period; they say nothing about consumption (#15740). The omission reads as
// zero only when the payload emits no usage scalars at all.
return (
period?.type === 'USAGE_PERIOD_TYPE_WEEKLY' &&
timestampsMatch(period.start, config.billingPeriodStart) &&
@@ -98,12 +99,39 @@ function hasConfirmedWeeklyPeriod(config: GrokBillingConfig): boolean {
)
}
// Why: proto3 JSON drops default zeros, so an omitted percent can mean zero —
// but only in a payload that emits no usage scalars at all. #15740 ships
// `onDemandUsed: {val: 0}`, proving this encoder does keep zeros, so there the
// omission means "not reported" and must never render as 0%.
function emitsExplicitUsageScalar(config: GrokBillingConfig): boolean {
const scalars = [
config.onDemandCap,
config.onDemandUsed,
config.prepaidBalance,
config.monthlyLimit,
config.used
]
return scalars.some((value) => parseMoneyVal(value) !== null)
}
function resolveWeeklyPercent(config: GrokBillingConfig): number | null {
const reported = config.creditUsagePercent
if (typeof reported === 'number' && Number.isFinite(reported)) {
return reported
}
if (reported !== undefined) {
return null
}
const computed = percentOfLimit(config.used, config.monthlyLimit)
if (computed !== null) {
return computed
}
return !emitsExplicitUsageScalar(config) && hasConfirmedWeeklyPeriod(config) ? 0 : null
}
function mapWeeklyCredits(config: GrokBillingConfig): RateLimitWindow | null {
const usedPercent =
config.creditUsagePercent === undefined && hasConfirmedWeeklyPeriod(config)
? 0
: config.creditUsagePercent
if (typeof usedPercent !== 'number' || !Number.isFinite(usedPercent)) {
const usedPercent = resolveWeeklyPercent(config)
if (usedPercent === null) {
return null
}
const periodEnd = config.currentPeriod?.end ?? config.billingPeriodEnd
@@ -122,16 +150,29 @@ function parseMoneyVal(value: GrokMoneyVal | undefined): number | null {
return typeof num === 'number' && Number.isFinite(num) ? num : null
}
// Why: the fetcher's only division — a zero, missing or unparseable denominator
// yields no percentage rather than NaN/Infinity or a fabricated 0%.
function percentOfLimit(
usedVal: GrokMoneyVal | undefined,
limitVal: GrokMoneyVal | undefined
): number | null {
const used = parseMoneyVal(usedVal)
const limit = parseMoneyVal(limitVal)
if (used === null || limit === null || limit <= 0) {
return null
}
return Math.min(100, Math.max(0, (used / limit) * 100))
}
function mapMonthlyUsage(config: GrokBillingConfig): RateLimitWindow | null {
const limit = parseMoneyVal(config.monthlyLimit)
const used = parseMoneyVal(config.used)
if (limit === null || used === null || limit <= 0) {
const usedPercent = percentOfLimit(config.used, config.monthlyLimit)
if (usedPercent === null) {
return null
}
const periodEnd = config.currentPeriod?.end ?? config.billingPeriodEnd
const resetsAt = periodEnd ? Date.parse(periodEnd) : null
return {
usedPercent: Math.min(100, Math.max(0, (used / limit) * 100)),
usedPercent,
windowMinutes: MONTHLY_WINDOW_MINUTES,
resetsAt: resetsAt !== null && Number.isFinite(resetsAt) ? resetsAt : null,
resetDescription: parseResetDescription(periodEnd)
@@ -150,14 +191,26 @@ function grokRequestHeaders(session: GrokAuthSession): Record<string, string> {
return headers
}
// Why: a flat response can carry monthly/on-demand fields and no percent at all
// (#15740); keying only on creditUsagePercent misreported those as "no config".
const FLAT_BILLING_FIELDS: readonly (keyof GrokBillingConfig)[] = [
'creditUsagePercent',
'currentPeriod',
'billingPeriodStart',
'billingPeriodEnd',
'subscriptionTier',
'monthlyLimit',
'used',
'onDemandCap',
'onDemandUsed',
'prepaidBalance'
]
function resolveBillingConfig(data: GrokBillingResponse): GrokBillingConfig | null {
if (data.config) {
return data.config
}
if (typeof data.creditUsagePercent === 'number') {
return data
}
return null
return FLAT_BILLING_FIELDS.some((field) => data[field] !== undefined) ? data : null
}
function billingUsageResult(
@@ -219,7 +272,7 @@ async function fetchBillingData(
}
type GrokMonthlyFallbackOutcome =
| { kind: 'window'; window: RateLimitWindow | null }
| { kind: 'window'; window: RateLimitWindow | null; config: GrokBillingConfig }
| { kind: 'result'; result: ProviderRateLimits }
// Why: request failures propagate as 'error' (thrown errors reach the caller's
@@ -235,7 +288,7 @@ async function fetchMonthlyUsageFallback(
return outcome
}
const config = outcome.data.config ?? outcome.data
return { kind: 'window', window: mapMonthlyUsage(config) }
return { kind: 'window', window: mapMonthlyUsage(config), config }
}
// Why: Orca never runs grok login; it only reads the session file the CLI updates.
@@ -286,7 +339,11 @@ export async function fetchGrokRateLimits(
if (fallback.window) {
return billingUsageResult({ monthly: fallback.window }, config, session)
}
return result('unavailable', 'Grok billing response did not include credit usage')
// Why: an account that reports spend fields but no computable percentage is
// not a quota-less plan — say the usage is unknown instead of implying zero.
return emitsExplicitUsageScalar(config) || emitsExplicitUsageScalar(fallback.config)
? result('unavailable', 'Grok did not report a usage percentage for this account')
: result('unavailable', 'Grok billing response did not include credit usage')
} catch (err) {
return result('error', err instanceof Error ? err.message : 'Grok usage request failed')
}
@@ -8,7 +8,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
getStatus: vi.fn(),
refreshGrokRateLimits: vi.fn()
refreshGrokRateLimits: vi.fn(),
grokUsage: vi.fn<() => unknown>(() => null)
}))
vi.mock('@/lib/agent-catalog', () => ({
@@ -29,7 +30,8 @@ vi.mock('../../store', () => ({
useAppStore: (selector: (state: Record<string, unknown>) => unknown) =>
selector({
refreshGrokRateLimits: mocks.refreshGrokRateLimits,
rateLimits: { grok: null }
settingsSearchQuery: '',
rateLimits: { grok: mocks.grokUsage() }
})
}))
@@ -45,6 +47,7 @@ describe('GrokAccountsSection', () => {
error: null
})
mocks.refreshGrokRateLimits.mockResolvedValue(undefined)
mocks.grokUsage.mockReturnValue(null)
Object.defineProperty(window, 'api', {
configurable: true,
value: { grokAccounts: { getStatus: mocks.getStatus } }
@@ -66,4 +69,23 @@ describe('GrokAccountsSection', () => {
).toBeInTheDocument()
expect(screen.queryByText(/grok login/i)).not.toBeInTheDocument()
})
// Why: #15740 — an unreported percentage must be stated, never shown as 0%.
it('shows why usage is unknown instead of hiding the row', async () => {
mocks.grokUsage.mockReturnValue({
provider: 'grok',
session: null,
weekly: null,
updatedAt: Date.now(),
error: 'Grok did not report a usage percentage for this account',
status: 'unavailable'
})
render(<GrokAccountsSection />)
expect(
await screen.findByText('Grok did not report a usage percentage for this account')
).toBeInTheDocument()
expect(screen.queryByText('0%')).not.toBeInTheDocument()
})
})
@@ -56,6 +56,12 @@ export function GrokAccountsSection(): React.JSX.Element {
// monthly included usage instead of hiding the usage row entirely.
const usageIsWeekly = Boolean(grokUsage?.weekly)
const usageWindow = grokUsage?.weekly ?? grokUsage?.monthly ?? null
// Why: hiding the row entirely left signed-in users with no explanation when
// Grok reports no percentage — never let unknown usage read as healthy (#15740).
const unavailableReason =
signedIn && !usageWindow && grokUsage?.status === 'unavailable'
? (grokUsage.error ?? null)
: null
return (
<section id="accounts-grok" className="space-y-4 scroll-mt-6">
@@ -198,6 +204,17 @@ export function GrokAccountsSection(): React.JSX.Element {
) : null}
</div>
</SearchableSetting>
) : unavailableReason ? (
<SearchableSetting
title={translate('auto.components.settings.GrokAccountsSection.0bb18642b7', 'Usage')}
description={translate(
'auto.components.settings.GrokAccountsSection.a8f4139350',
'Grok reported no usage percentage for this account.'
)}
keywords={['grok', 'xai', 'usage', 'credits', 'oauth']}
>
<p className="text-xs text-muted-foreground">{unavailableReason}</p>
</SearchableSetting>
) : null}
</section>
)
+3 -1
View File
@@ -10557,7 +10557,9 @@
"e6dadc1e2b": "Monthly usage",
"75e396bf42": "Included monthly usage for Grok unified-billing accounts.",
"b36fa2c908": "Signed in. Orca reads the Grok CLI session stored on disk.",
"f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed."
"f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed.",
"0bb18642b7": "Usage",
"a8f4139350": "Grok reported no usage percentage for this account."
},
"AppearanceWindowSidebarSection": {
"usagePercentageDisplayUsed": "Used",