Repository navigation
Description
fix(ai): quota toNumber coerces empty strings to 0 — an empty upstream field reports a pool 100% used (or 0% used)
Verified 2026-11-05 on origin/main b318603c68 (laptop primary, clean), probe at ~/git/fleet/probe-2026-11-05-creator-opencode-1235/ on the laptop (probe.ts, probe2.ts, bun 1.4.4, run twice with identical results).
The bug
packages/ai/src/quota/utils.ts:26-31 — the shared numeric parser for the whole quota suite:
export const toNumber = (value: JsonValue | undefined): number | null => {
if (typeof value === "number" && Number.isFinite(value)) return value
if (typeof value === "string") {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : null
}
return null
}Number("") === 0 and Number(" ") === 0, both finite — so an empty or whitespace-only string is parsed as a real zero, not as "no value" (null). Probe: toNumber("") = 0, toNumber(" ") = 0, toNumber("12") = 12.
toNumber is the front door for ~25 call sites across 9 quota providers (codex used_percent/balance/spend limits, claude percents + money, deepseek balances, opencode-go percent/balance/expires, opendesign credits, command-code credits, antigravity activeIndex, meta key-endpoint percent). Every one of them inherits the flaw.
Reproduced consequences (probe, payload-shaped)
parseCommandCodeCredits({ credits: { monthlyCredits: "" } })→ monthly windowusedPercent: 100, label "10 / 10" — an empty field reports the Command Code monthly pool fully exhausted. Quota-aware routing and theopencode quotadisplay both reason from this.parseOpenCodeGoUsage({ usage: { rolling: { percent: "", resetsAt: 1893456000 } } })→ a kept5hwindow withusedPercent: 0— an empty field reports a pool completely untouched (false headroom, the opposite error).- Same shape elsewhere by construction: codex
credits.balance: ""→ a real $0 balance; opencode-goexpires: ""→ expiry 0 (1970).
Providers do emit empty strings for unset fields (that is why sibling helpers exist: asNonEmptyString in the same file treats ""/blank as absent, and cline-pass's caller already guards its percent through it). toNumber is the one parser that does not.
The duplicate
packages/ai/src/quota/providers/meta.ts:73-77 carries a local num helper with the identical Number(value) body and the identical flaw: window_weighted_used: "" parses to 0 and passes parseSubscriptionQuota's null gate (:84-89), minting windows from empty fields. One fix should land in the shared helper and the duplicate should delegate to it.
Why it matters
These numbers feed the quota display and quota-aware routing (#587/#566 consumers): a false 100% steers sessions away from a live pool; a false 0% steers them into a walled one. Both errors are silent — the window looks well-formed either way. The fix is one guard in one shared function, plus pinning tests in the pattern #1722/#1725 established.
Out of scope (checked, already safe): groq's headerNumber (groq.ts:55-59 guards !raw first; its regex parser cannot match an empty capture), cline-pass's percent (goes through asNonEmptyString first).
Acceptance criteria
-
toNumberreturnsnullfor empty and whitespace-only strings (and still parses"12"," 12 ", numeric strings with signs/decimals as before). - meta.ts's local
numdelegates to the sharedtoNumber(or is otherwise fixed identically) — no second copy of the flaw. - Tests in
packages/ai/test/quota.test.ts(or a wired sibling) pin:toNumber("")/toNumber(" ")→ null;parseCommandCodeCreditswithmonthlyCredits: ""produces no monthly window;parseOpenCodeGoUsagewithpercent: ""produces no window for that field. - Existing quota tests still pass under the ai package test command; ai typecheck clean.
Activity
dezren39 commentedon Nov 5, 2026
🤖 Agent note · state: filed
toNumbercoerces empty/whitespace strings to 0 — verified on laptop probe (monthlyCredits:"" → 100% used "10 / 10"; percent:"" → 0% used window). Evidence in the issue body; probe~/git/fleet/probe-2026-11-05-creator-opencode-1235/on the laptop.dezren39 commentedon Nov 5, 2026
🤖 Agent note · state: picked up
toNumberblank-string coercion in packages/ai/src/quota/utils.ts, delegate meta.ts's duplicatenumto it, add pinning tests.Merge pull request #1742 from developing-today-forks/creator/1741-quo…
dezren39 commentedon Nov 5, 2026
🤖 Agent note · state: done
toNumberreturns null for blank strings; meta.ts duplicatenumdeleted in favor of the shared helper; 4 pinning tests added.Merge pull request #2117 from developing-today-forks/queue/12901-1934…