Repository navigation
Description
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
Verified 2026-11-05 on main 23d68ea41451eea55dde7ce5fd59dc0e218a8801 (laptop primary, fresh fetch/ff), with a probe run on the laptop (~/git/fleet/probe-2026-11-05-codex-quota/probe.ts) driving the real provider.fetchQuota from packages/ai/src/quota/providers/codex.ts against a mocked ChatGPT wham/usage response.
Evidence
packages/ai/src/quota/providers/codex.ts:104 and :112 publish the primary and secondary rate-limit windows into a plain record keyed only by the derived duration label:
windows[resolveWindowLabel(windowSeconds)] = toUsageWindow({ ... }) // primary, :104
windows[resolveWindowLabel(windowSeconds)] = toUsageWindow({ ... }) // secondary, :112resolveWindowLabel (packages/ai/src/quota/utils.ts:64) returns "tokens" for a null/zero duration, "5h" for 18000, "weekly" for 604800, etc. — it has no notion of which window it is labelling. The two windows therefore share a key whenever their durations collide, and the secondary silently overwrites the primary.
Probe results (mocked payload → published usage.windows):
- Baseline (primary 18000s/10%, secondary 604800s/80%): both windows published (
5h,weekly) — correct. - Both windows missing
limit_window_seconds(primary 10%, secondary 80%): only ONE window survives —tokens: { usedPercent: 80, ... }. The primary's 10% is silently lost; the result still reportsok: true. - Both windows at 18000s: same loss — only
5h: { usedPercent: 80 }survives. primary_window: {}(empty object): a phantom window is minted —tokens: { usedPercent: null, remainingPercent: null, windowSeconds: null, resetAt: null, ... }— and the result isok: truewith a window that carries no data at all.- Primary with
limit_window_seconds+reset_atbut noused_percent: publishes a5hwindow whoseusedPercent/remainingPercentare both null.
There is no test net for any of this: packages/ai/test/quota.test.ts contains no Codex describe/block at all (grep for codex/Codex in that file returns nothing), while sibling providers (mistral, meta, turso, groq, command-code) are pinned there. This is the same unpinned-parser class as #1725/#1750, with a live data-loss bug inside it.
Why it matters
Quota windows drive placement and exhaustion decisions (design #587: one quota signal, three consumers). A dropped primary window means the fleet sees only the looser/looser-looking limit; a phantom all-null window means a provider with no usable rate-limit data still presents a window downstream consumers must guess at. Both failures are silent — ok: true either way — so nothing downstream can tell data was lost.
Acceptance criteria
- Primary and secondary windows can never overwrite each other: when their derived labels collide (equal durations, or either duration missing), both windows are published under distinct, stable keys (the fix names the keying scheme — e.g. primary/secondary-qualified keys — and records what downstream consumers see for the normal 5h/weekly case, which must not change).
- A window object carrying no usable data (no
used_percent, no duration, no reset) does not mint a published window; if neither window has usable data, the result follows the sibling providers' no-data contract (cf. cline-pass/deepseek:ok: false, "No quota data in response") or the issue records why a different contract is right for Codex. - A window with a duration/reset but no
used_percentis dispositioned explicitly (kept with null percent, or dropped) — not left as an accident of the current code. - New tests in
packages/ai/test/quota.test.tspin the Codex mapping: baseline 5h/weekly, missing-duration collision, equal-duration collision, empty window object, and missingused_percent(oracle: the mockedwham/usagepayload's own fields — every published window must trace to a distinct payload window with matching percent/reset). - Existing quota tests still pass.
Activity
dezren39 commentedon Nov 5, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 5, 2026
🤖 Agent note · claimed
dezren39 commentedon Nov 5, 2026
🤖 Agent note · triaged
Merge pull request #1770 from developing-today-forks/queue/10715-1762…
developing-today commentedon Nov 5, 2026
Merged — PR #1770 landed as f67f1ae and this issue auto-closed. Verification on the PR branch (laptop):
bun test test/quota.test.ts31/31 pass; both ai tsgo configs exit 0; PR mergeable CLEAN, no Codex reviews posted.Completed by: lane opencode-a, task 10715