Repository navigation
Description
fix(ai): claude quota extra-usage spend publishes negative amounts — "$-5.00 / $10.00", and a negative-only payload still returns ok:true
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) converts a money object withminor / 10 ** exponentand no sign check — a negativeamount_minoryields a negative amount.- The spend block (:206-213) formats that amount straight into the
extra_usagewindow's label viaformatSpendLabel(:136-142). The window'susedPercentcomes from the separate wirespend.percent(clamped), so nothing cross-checks the label's sign. addWindow(:156-166) publishes the window whenever a percent OR a valueLabel exists — so a spend object whose only figure is a negative amount still mints anextra_usagewindow, andfetchQuotathen returnsok: true(the fix(ai): claude quota returns ok:true with empty windows for a 200 payload carrying no usable quota data #1915 no-data contract only fires when windows AND models are both empty).
A negative spend/limit is a figure that cannot exist. Sibling code in this same quota module treats it as absent: meta's parseBillingBanner maps a negative remaining_minor_units to null (pinned by test), turso's numNonNegative, opencode-go's toMicroCents (rejects < 0), and command-code's monthlyRemaining >= 0 gate (#1744) all read a negative amount as no data. OpenRouter's identical unlimited-path defect was filed as #1913. The claude spend path is the remaining raw-negative money label.
Runtime probe (laptop worktree, probe-claude-spend.ts, kept in the worktree)
Driving the real exported toClaudeUsage:
{ spend: { enabled: true, percent: 50, used: { amount_minor: -500, exponent: 2, currency: "USD" }, limit: { amount_minor: 1000, exponent: 2, currency: "USD" } } }→extra_usagewindow,valueLabel: "$-5.00 / $10.00".- Same with the limit negative (
amount_minor: -1000) →valueLabel: "$5.00 / $-10.00". { spend: { enabled: true, used: { amount_minor: -500, exponent: 2, currency: "USD" } } }(no percent, no limit) →extra_usagewindow withusedPercent: nullandvalueLabel: "$-5.00"— the negative amount is the window's ONLY figure, so throughfetchQuotathis payload alone publishesok: true.- Controls: positive
{ amount_minor: 500 } / { amount_minor: 1000 }→"$5.00 / $10.00"(correct); zero used →"$0.00 / $10.00"(correct);{ spend: { enabled: true } }with no figures → no window (correct, unchanged).
Why it matters
The extra_usage window is the paid-overage signal for a Claude subscription: downstream consumers read its label as money actually spent against a real limit. A negative spend is impossible wire data (a billing glitch, a refund/adjustment spelled as spend, a mimicking gateway); publishing it as ok: true quota data — in the negative-only case as the provider's sole result, defeating the #1915 no-data contract in spirit — teaches consumers a figure that cannot be true, in the one shape (money amounts) where this fleet has repeatedly chosen "bad data reads as absent" over fabrication.
Acceptance criteria
- A negative
amount_minoronspend.usedorspend.limitgets one explicit disposition — read as absent (the metaparseBillingBannerprecedent in this same module) is the sibling-consistent choice; any other disposition is chosen and recorded in the closing note. - A spend payload whose only figure is a negative amount publishes no
extra_usagewindow, so throughfetchQuotait reaches the existing "No quota data in response" contract when nothing else is usable. - Positive and zero amounts are unchanged (
$5.00 / $10.00,$0.00 / $10.00controls), and a valid percent-only spend window still publishes. -
packages/ai/test/quota.test.tspins the negative-used, negative-limit, and negative-only cases beside the existing claude describes (the spend path currently has no tests at all). - Existing quota suite stays green.
Dedup
Searched open + closed issues this run for claude spend / negative amount / amount_minor: no covering issue. #1913 (open) is OpenRouter's unlimited-path usage_monthly, a different provider and field; #1915 (closed) is the figure-less-200 no-data contract for limits/legacy windows and does not touch the spend block; #1882 (open) is codex spend_control, a different provider. The spend path in claude.ts has no test coverage today (no spend/extra_usage cases in quota.test.ts beyond a meta zero-limit test that mentions the word).
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); related fix(ai): openrouter quota unlimited path publishes a negative spend — "$-5.00 spent" for usage_monthly below zero #1913 (openrouter negative spend), fix(ai): claude quota returns ok:true with empty windows for a 200 payload carrying no usable quota data #1915 (no-data contract)dezren39 commentedon Nov 7, 2026
🤖 Agent note · adopted as design phase 2 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: issue open, no hard-stop labels, triage fix spec clear; next: branch_createddezren39 commentedon Nov 7, 2026
Progress from
repo-worker-opencode: implementation_complete: toAmount reads negative amount_minor as absent; tests added in quota.test.ts; next: targeted testsdezren39 commentedon Nov 7, 2026
Progress from
repo-worker-opencode: pr_opened: PR #1957; targeted tests 145 pass; next: check CI/Codex then mergedezren39 commentedon Nov 7, 2026
Released by
repo-worker-opencode(outcome: done). fixed_merged: PR #1957 merged 8cd974d; toAmount reads negative amount_minor as absent; quota.test.ts 145 pass on laptop; issue auto-closeddezren39 commentedon Nov 7, 2026
🤖 Agent note · done