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

mapWeeklyCredits treated an absent creditUsagePercent as a confirmed protobuf
zero whenever the weekly period matched billing bounds, so unified-billing
accounts whose credits view never reports the percent showed a confident 0%
and short-circuited the monthly fallback (#15740). Those payloads emit
onDemandUsed/prepaidBalance zeros, which disproves the "encoder drops zeros"
premise.

Resolution order is now: reported percent → monthly used/monthlyLimit pair as
a monthly window → synthetic 0 only when the payload emits no usage scalars at
all and the weekly period is confirmed → unavailable with an explicit reason
the Accounts pane surfaces.

Rebased onto current main from nwparker/grok-usage-percent-fallback (#15878).

Fixes #15740

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
This commit is contained in:
TimothyVang
2026-09-06 19:25:57 -07:00
committed by GitHub
co-authored by Neil
parent e48d83a5e1
commit 4934920f06
5 changed files with 258 additions and 18 deletions
+135
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,138 @@ 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)
})
// Why: the monthly budget pair is a monthly window wherever it arrives — the
// credits view must not relabel it 'Weekly credits'.
it('publishes a credits-view monthly budget pair as a monthly window without a second request', 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).toBeNull()
expect(result.monthly?.usedPercent).toBe(25)
expect(result.monthly?.windowMinutes).toBe(43_200)
expect(netFetchMock).toHaveBeenCalledTimes(1)
})
// Why: #9214/#9219 — non-zero money fields never prove the encoder emits
// default zeros, so the omitted percent still reads as the dropped zero.
it('still reads an omitted percentage as zero when the payload carries only non-zero money fields', async () => {
authState.file = freshAuthJson()
netFetchMock.mockResolvedValueOnce(
jsonResponse({
config: {
currentPeriod: {
type: 'USAGE_PERIOD_TYPE_WEEKLY',
start: '2026-07-17T19:38:56.948570+00:00',
end: '2026-07-24T19:38:56.948570+00:00'
},
billingPeriodStart: '2026-07-17T19:38:56.948570+00:00',
billingPeriodEnd: '2026-07-24T19:38:56.948570+00:00',
onDemandCap: { val: 100 },
prepaidBalance: { val: 25 },
isUnifiedBillingUser: true
}
})
)
const result = await fetchGrokRateLimits()
expect(result.status).toBe('ok')
expect(result.weekly?.usedPercent).toBe(0)
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).toBeNull()
expect(result.monthly?.usedPercent).toBe(25)
expect(result.monthly?.windowMinutes).toBe(43_200)
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()
+79 -15
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), so resolveWeeklyPercent
// rules out the other consumption evidence before trusting this.
return (
period?.type === 'USAGE_PERIOD_TYPE_WEEKLY' &&
timestampsMatch(period.start, config.billingPeriodStart) &&
@@ -98,12 +99,49 @@ function hasConfirmedWeeklyPeriod(config: GrokBillingConfig): boolean {
)
}
function usageScalars(config: GrokBillingConfig): (GrokMoneyVal | undefined)[] {
return [
config.onDemandCap,
config.onDemandUsed,
config.prepaidBalance,
config.monthlyLimit,
config.used
]
}
// Why: proto3 JSON drops default zeros, so an omitted percent can mean zero —
// but only an explicitly-emitted zero proves this encoder keeps them. #15740
// ships `onDemandUsed: {val: 0}`, so there the omission means "not reported"
// and must never render as 0%. Non-zero money fields prove nothing either way,
// so #9214/#9219 accounts that carry only those keep their genuine 0%.
function emitsExplicitZeroScalar(config: GrokBillingConfig): boolean {
return usageScalars(config).some((value) => parseMoneyVal(value) === 0)
}
function reportsAnyUsageScalar(config: GrokBillingConfig): boolean {
return usageScalars(config).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
}
// Why: infer the dropped zero only when nothing else in the payload speaks
// for consumption — an explicit zero proves the encoder keeps defaults, and a
// computable budget pair is a real monthly number this must not shadow.
if (emitsExplicitZeroScalar(config) || mapMonthlyUsage(config) !== null) {
return null
}
return 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
@@ -125,13 +163,16 @@ function parseMoneyVal(value: GrokMoneyVal | undefined): number | null {
function mapMonthlyUsage(config: GrokBillingConfig): RateLimitWindow | null {
const limit = parseMoneyVal(config.monthlyLimit)
const used = parseMoneyVal(config.used)
// Why: a zero, missing or unparseable denominator yields no window rather
// than NaN/Infinity or a fabricated 0%.
if (limit === null || used === null || limit <= 0) {
return null
}
const usedPercent = Math.min(100, Math.max(0, (used / limit) * 100))
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.
@@ -277,6 +330,13 @@ export async function fetchGrokRateLimits(
if (weekly) {
return billingUsageResult({ weekly }, config, session)
}
// Why: the credits view can already carry the monthly budget pair; that pair
// is a monthly window, so publish it as one rather than mislabelling it
// weekly — and skip the redundant second request.
const creditsMonthly = mapMonthlyUsage(config)
if (creditsMonthly) {
return billingUsageResult({ monthly: creditsMonthly }, config, session)
}
// Why: some unified-billing accounts expose only a monthly included budget;
// their credits view omits creditUsagePercent, so read the default view.
const fallback = await fetchMonthlyUsageFallback(session, options.signal)
@@ -286,7 +346,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 reportsAnyUsageScalar(config) || reportsAnyUsageScalar(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
@@ -11051,7 +11051,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",