Repository navigation
Description
fix(ai): antigravity quota summary path publishes figure-less buckets as all-null-percent windows
Verified 2026-11-06 on origin/main 10f305865f (merge of PR #1732), in a laptop worktree (~/git/fleet/probe-2026-11-06-creator-2235/wt, kept).
The defect
packages/ai/src/quota/providers/antigravity.ts, parseBucket (~:271-307) — the summary path, distinct from the per-model fallback filed as #1912:
const remainingFraction = toNumber(record?.remainingFraction)
const remainingPercent = remainingFraction !== null ? Math.round(remainingFraction * 100) : null
const usedPercent = remainingPercent !== null ? Math.min(100, Math.max(0, 100 - remainingPercent)) : null
...
return { label, seconds, remainingFraction, window: toUsageWindow({ usedPercent, windowSeconds: seconds, resetAt }) }Once the bucket's window string is recognized (5h / weekly / daily), the bucket publishes its window unconditionally — including when remainingFraction is absent, so usedPercent and remainingPercent are both null and the window carries no quota figure at all: only a duration and a reset the payload happened to include. transformSummary (:322-330) stores that window in the group's windows (keep-first), and :356-375) then republishes it as every matched model's window set.transformModels' matched-group branch (
That is the phantom-window shape window-contract rule 4 in packages/ai/src/quota/types.ts forbids ("No figure, no window — an empty/all-null/zero-limit window-shaped object publishes no window"), and the reset-only spelling two siblings disposition explicitly:
- codex (fix(ai): codex quota windows keyed only by derived duration label — a missing or shared limit_window_seconds silently drops one window, and an empty window object mints a phantom all-null window #1762): "a duration/reset with no percent is dispositioned the same way (dropped, not published with a null percent)".
- meta key-endpoint (fix(ai): meta quota key-endpoint path publishes phantom all-null windows for figure-less subs_usage objects #1907): figure-less objects dropped instead of publishing all-null windows beside real ones.
#1768's fix taught this path to propagate a missing fraction as null instead of inventing 1-as-fully-remaining — but null propagation into a published window is still publication: the window exists, with a fabricated kind (the recognized string) and no figure.
Runtime probe (laptop worktree, probe-creator.ts, kept in the worktree)
Driving the real exported parseBucket / transformSummary / transformModels:
parseBucket({ window: "5h", resetTime: "2026-12-01T00:00:00Z" })(reset + kind, no fraction) → a5hwindow withusedPercent: null, remainingPercent: null, windowSeconds: 18000and the reset populated.transformSummary({ groups: [{ displayName: "Gemini", buckets: [{ window: "5h", resetTime: … }] }] })→ the group's windows carry that same all-null-percent5hwindow, and it becomes the group'sbucket5h.transformModels({ models: { "gemini-x": {} } }, <that group>)→"gemini-x"inherits the phantom5hwindow verbatim.- Control
parseBucket({ window: "5h", remainingFraction: 0.5, resetTime: … })→ real window, 50% used / 50% remaining (correct, unchanged). - Control
parseBucket({})→null(an unrecognized/absent window string is already skipped, per fix(ai): antigravity quota buckets keyed only by derived label — an unrecognized bucket overwrites the real 5h window, same-label buckets silently drop, missing remainingFraction counts as fully remaining #1768 — only the recognized-but-figure-less case slips through).
Because fetchWithCandidates returns ok: true whenever at least one model entry exists, a summary whose buckets are all figure-less reads as a successful quota read whose every model carries an all-null-percent window.
Why it matters
The summary path is the primary path — it serves every model that matches a group, which is the normal case; the #1912 fallback only serves unmatched models. A figure-less bucket (an upstream shape change, a bucket reported before its first figure, a new window kind spelled with a familiar string) currently manufactures a window per group and per model from a reset timestamp alone, telling downstream consumers a 5-hour/weekly window exists with unknown usage instead of the honest absence. It also feeds the weekly-constrained ordering in transformModels, whose comparisons the code itself says require the fractions to actually be present (#1768 comment, ~:357-362).
Acceptance criteria
- A bucket whose
remainingFractioncarries no usable figure publishes no window (theparseBucket-returns-null disposition, matching its unrecognized-kind case, is the precedent — or another single disposition, chosen and recorded in the closing note). -
transformSummarygroups built only from figure-less buckets carry no windows from those buckets, and matched models inherit none. - The real-figure control is unchanged: a present
remainingFractionstill publishes its window with the existing label derivation and keep-first collision behaviour. -
packages/ai/test/quota.test.tspins: reset-only bucket, figure-less summary group, matched-model inheritance, and the real-fraction control. - Existing quota suite stays green; the per-model fallback path is fix(ai): antigravity quota transformModels fallback publishes a phantom all-null "5h" window for models with no quota figure #1912's scope and is unchanged by this fix.
Dedup
Searched open + closed issues for "antigravity parseBucket", "antigravity summary phantom", and "antigravity transformModels" this run: no covering issue. #1912 (open) is the per-model fallback branch and explicitly scopes the summary-group path out ("the summary-group path is unchanged"). #1768 (closed) covered bucket labelling, keep-first, and missing-fraction-as-1 in this path; it did not cover a recognized bucket with no fraction still publishing a window. #1909 (open) is only about the stale inventory rows.
Activity
developing-today commentedon Nov 7, 2026
🤖 Agent note · filed
gh issue createcalls returned exit 0 with empty output and created nothing (the secondary-rate-limit silent-drop shape fleet_paperwork.py documents).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
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: parseBucket at main 59834b publishes figure-less recognized buckets; next: worktree + fix (parseBucket returns null when remainingFraction has no usable figure, rule 4)dezren39 commentedon Nov 7, 2026
Progress from
repo-worker-opencode: implementation_complete: parseBucket returns null on figure-less buckets; tests 137 pass, negative control 5 fail stashed; next: commit + PRMerge pull request #1940 from developing-today-forks/queue/1917-1917-…
dezren39 commentedon Nov 7, 2026
Released by
repo-worker-opencode(outcome: done). fixed_merged via PR #1940 MERGED 05702ca: parseBucket returns null when remainingFraction has no usable figure (rule 4, parseBucket-returns-null disposition); figure-less groups carry no windows, matched models inherit none; fallback path #1912 unchanged. Tests: bun test test/quota.test.ts 137 pass/0 fail, negative control 5 fail with fix stashed; typecheck only pre-existing @opencode/util/http-body errors (identical on clean tree). Codex: no findings posted on PR (checked; siblings #1937/#1938 also none). Issue CLOSED.developing-today commentedon Nov 7, 2026
🤖 Agent note · done