Repository navigation
Description
fix(ai): claude quota spend toAmount trusts the wire exponent — exponent 999 publishes "$0.00 / $0.00" for a real $5/$10 spend, exponent -2 inflates it 100x
Verified 2026-11-07 on origin/main cfc5e3d82c (merge of PR #1916), in a laptop worktree (~/git/fleet/probe-2026-11-06-creator-2335, kept).
The defect
packages/ai/src/quota/providers/claude.ts, toAmount (:128-133):
const minor = toNumber(money?.amount_minor)
if (minor === null) return null
const exponent = toNumber(money?.exponent) ?? 2
return minor / 10 ** exponentThe divisor exponent is taken from the wire with no range validation at all. Money exponents are a tiny closed set in practice (ISO 4217 currency exponents are 0-3; Anthropic's payload uses 2). An out-of-range exponent silently rescales a real amount into a false one instead of reading as absent:
- A large exponent underflows the amount to zero — a real spend publishes as
$0.00, the most misleading value a quota label can carry (it reads as "nothing spent"). - A negative exponent multiplies the amount — a $5 spend publishes as
$500.00.
Every other numeric field in this quota module is range-checked at the boundary (percents clamped, timestamps null on non-positive, zero/negative limits read as no-data #1799/#1750, negative banner amounts read as null). The exponent — the one field that scales all the others — is the exception. Disposition of the amount's SIGN is a separate issue (negative amount_minor publishing raw); this issue is only the exponent's range.
Runtime probe (laptop worktree, probe-claude-spend.ts, kept in the worktree)
Driving the real exported toClaudeUsage with spend: { enabled: true, percent: 50, used: {...}, limit: {...} }:
used { amount_minor: 500, exponent: 999 },limit { amount_minor: 1000, exponent: 999 }→extra_usagewindow,valueLabel: "$0.00 / $0.00"(the real $5.00 / $10.00 spend erased to zero).used { amount_minor: 5, exponent: -2 },limit { amount_minor: 10, exponent: -2 }→valueLabel: "$500.00 / $1000.00"(100x inflation).- Control
exponent: 2→"$5.00 / $10.00"(correct, unchanged).
Why it matters
The extra_usage label is the paid-overage signal for a Claude subscription. A corrupted exponent — a buggy or mimicking gateway, a payload shape change — currently converts directly into a fabricated spend figure with no trace: either a false zero (headroom that does not exist, the failure mode the window contract exists to prevent) or a 100x overstatement. Reading an implausible exponent as absent costs nothing: the percent figure on the same window still publishes, and a spend object with no other figure falls to the no-data contract.
Acceptance criteria
- An
exponentoutside one explicit, recorded valid range (the ISO 4217 0-3 range is the natural choice; the chosen range and its rationale are recorded in the closing note) makestoAmountread that money object as absent (null), consistent with the module's other boundary dispositions. - In-range exponents (0, 2, 3) and the absent-exponent default of 2 are unchanged (
$5.00 / $10.00control). - A spend window whose percent is valid still publishes on the percent when its amounts read as absent.
-
packages/ai/test/quota.test.tspins the over-range, negative-exponent, and in-range control cases in the claude spend coverage. - Existing quota suite stays green.
Dedup
Searched open + closed issues this run for claude spend / exponent / amount_minor: no covering issue. No test in quota.test.ts exercises the claude spend path's exponent at all. The negative-amount disposition for the same function is filed separately this run (sign and scale are different defects with different fixes); #1913 (open, OpenRouter negative spend) and #1799 (closed, zero-limit) are different providers/fields.
Activity
developing-today commentedon Nov 7, 2026
🤖 Agent note · filed
probe-claude-spend.tsin laptop worktree~/git/fleet/probe-2026-11-06-creator-2335(kept); sibling sign defect filed this run as fix(ai): claude quota extra-usage spend publishes negative amounts — "$-5.00 / $10.00", and a negative-only payload still returns ok:true #1919dezren39 commentedon Nov 7, 2026
🤖 Agent note · adopted as design phase 3 by repo design architect (opencode pass 2026-11-07)
dezren39 commentedon Nov 7, 2026
Picked up by
repo-worker-opencode— working this now. (Fleet queue fallback mode: the queue backend is down, so this issue's comments carry the claim. The claim stays live whilerepo-worker-opencodeposts progress markers; after 30 minutes of silence any other worker may pick this up.)dezren39 commentedon Nov 7, 2026
Progress from
repo-worker-opencode: verified: toAmount at claude.ts trusts wire exponent, no range check at origin/main c342ba5; next: triage note + route small-fix9 remaining items
dezren39 commentedon Nov 7, 2026
Progress from
repo-worker-opencode: verified: issue open, no hard stops, task/small-fix after prior triage; next: create worktree and implement toAmount exponent range 0-3dezren39 commentedon Nov 7, 2026
Progress from
repo-worker-opencode: implementation_complete: toAmount exponent range 0-3 in claude.ts + quota.test.ts pins (999, -2, controls 0/2/3/absent); local bun test blocked by uninstalled deps in fresh worktree, verifying via CI per CI-first; next: commit, push, PRdezren39 commentedon Nov 7, 2026
Progress from
repo-worker-opencode: targeted_tests_passed: quota.test.ts 140 pass 0 fail on laptop, typecheck pass, PR #1949 open, no Codex findings; next: mergeMerge pull request #1949 from developing-today-forks/queue/1920-claud…
dezren39 commentedon Nov 7, 2026
🤖 Agent note · done
toAmountexponent range check.bun test test/quota.test.ts140 pass / 0 fail (3 new fix(ai): claude quota spend toAmount trusts the wire exponent — exponent 999 publishes "$0.00 / $0.00" for a real $5/$10 spend, exponent -2 inflates it 100x #1920 pins: 999, -2, controls);bun run typecheckpass. No Codex findings on the PR.dezren39 commentedon Nov 7, 2026
Released by
repo-worker-opencode(outcome: done). fixed_merged: PR #1949 merged aca6992, issue closed; toAmount exponent range ISO 4217 integers 0-3, out-of-range reads absent; quota.test.ts 140 pass/0 fail + typecheck pass (laptop); outcome=fixed_merged issue_state=closed branch=queue/1920-claude-exponent-range ci=not_applicable (repo CI is main-only; no PR checks reported)