Problem Statement
The preemptive quota scheduler defers a near/fully exhausted account for Math.min(longestWait, maxDeferralMs) (lib/preemptive-quota-scheduler.ts:290), with DEFAULT_MAX_DEFERRAL_MS = 2h (lib/preemptive-quota-scheduler.ts:30, config preemptiveQuotaMaxDeferralMs).
For a weekly (7d) or monthly window that is fully exhausted with a cached reset timestamp days away, this means: the preemptive deferral expires after 2 hours → the account becomes preemptively eligible again → the next selection can route a live request into a guaranteed 429 → the reactive path then re-benches it from the reset headers (getQuotaNearExhaustionWaitMs clamps at 7d). Net effect: a periodic wasted request + cooldown churn against an account that is known (from cached reset data) to be dead for days. The reset timestamps are already fetched and cached since 2.7.0, so the information needed to defer correctly exists.
(We hit this with a weekly-exhausted account in a 3-account pool; the churn is modest but systematic, and it makes forecast/selection behavior harder to reason about during incidents.)
Proposed Solution
Make the deferral bound window-aware instead of a flat cap:
- When the exhausted window's cached
resetAtMs is finite and trusted, allow deferral up to that reset (optionally with a safety ceiling, e.g. the existing 7d MAX_RATE_LIMIT_DELAY_MS clamp).
- Keep the current 2h cap as the fallback when reset data is missing, bogus, or stale — preserving the existing defense-in-depth rationale against bogus upstream reset headers.
- Optionally: make
preemptiveQuotaMaxDeferralMs per-window (5h vs 7d/monthly) instead of global.
Expected impact: fully-exhausted long-window accounts stop cycling through "preemptive release → live 429 → reactive re-bench" every 2 hours; selection and forecast stay consistent with the cached quota reality.
Alternatives Considered
- Raising
preemptiveQuotaMaxDeferralMs globally (works today as an operator override, but a flat global value either under-defers long windows or over-defers 5h windows).
- Pausing the exhausted account manually until its weekly reset (our current workaround; requires operator attention).
- Doing nothing: the reactive 429 path does self-correct, at the cost of one live failed request per cycle.
Scope
In scope: the deferral bound computation in the preemptive quota scheduler and its config surface. Out of scope: reactive rate-limit handling, quota probing, scoring.
Validation Plan
- Unit: snapshot with
usedPercent=100 and resetAtMs = now + 72h → deferral equals 72h (window-aware path), not 2h; snapshot with missing/invalid resetAtMs → falls back to the 2h cap.
- Command-level: with a weekly-exhausted account, observe (e.g. via
report --explain / router logs) that no live request is routed to it between preemptive deferrals until the cached reset passes.
Compliance Confirmation
Problem Statement
The preemptive quota scheduler defers a near/fully exhausted account for
Math.min(longestWait, maxDeferralMs)(lib/preemptive-quota-scheduler.ts:290), withDEFAULT_MAX_DEFERRAL_MS = 2h(lib/preemptive-quota-scheduler.ts:30, configpreemptiveQuotaMaxDeferralMs).For a weekly (7d) or monthly window that is fully exhausted with a cached reset timestamp days away, this means: the preemptive deferral expires after 2 hours → the account becomes preemptively eligible again → the next selection can route a live request into a guaranteed 429 → the reactive path then re-benches it from the reset headers (
getQuotaNearExhaustionWaitMsclamps at 7d). Net effect: a periodic wasted request + cooldown churn against an account that is known (from cached reset data) to be dead for days. The reset timestamps are already fetched and cached since 2.7.0, so the information needed to defer correctly exists.(We hit this with a weekly-exhausted account in a 3-account pool; the churn is modest but systematic, and it makes
forecast/selection behavior harder to reason about during incidents.)Proposed Solution
Make the deferral bound window-aware instead of a flat cap:
resetAtMsis finite and trusted, allow deferral up to that reset (optionally with a safety ceiling, e.g. the existing 7dMAX_RATE_LIMIT_DELAY_MSclamp).preemptiveQuotaMaxDeferralMsper-window (5hvs7d/monthly) instead of global.Expected impact: fully-exhausted long-window accounts stop cycling through "preemptive release → live 429 → reactive re-bench" every 2 hours; selection and forecast stay consistent with the cached quota reality.
Alternatives Considered
preemptiveQuotaMaxDeferralMsglobally (works today as an operator override, but a flat global value either under-defers long windows or over-defers 5h windows).Scope
In scope: the deferral bound computation in the preemptive quota scheduler and its config surface. Out of scope: reactive rate-limit handling, quota probing, scoring.
Validation Plan
usedPercent=100andresetAtMs = now + 72h→ deferral equals 72h (window-aware path), not 2h; snapshot with missing/invalidresetAtMs→ falls back to the 2h cap.report --explain/ router logs) that no live request is routed to it between preemptive deferrals until the cached reset passes.Compliance Confirmation