Repository navigation
feat(quota): opt-in auto-ping to keep Codex quota windows warm (#6977) - #6995
Conversation
Codex's rolling "session" quota window only starts counting down once a
request lands inside it, so an idle connection's window keeps sliding
forward and the first real request after a long idle period pays for the
whole warm-up latency. This adds a strictly opt-in, per-connection
scheduler (default OFF) that watches an enabled connection's reported
resetAt and, once it slides forward, fires one tiny non-billed-model
request through the real Codex executor to keep the window warm.
- New in-process scheduler (src/lib/services/quotaAutoPing.ts), fully
dependency-injected (settings, DB, credential refresh, usage fetch,
executor, circuit breaker) and clock-injectable for deterministic tests.
Reimplemented in TS from the shipped 9router
src/shared/services/quotaAutoPing.js (Codex half only).
- Migration 123: last_ping_at / last_pinged_reset_key on
provider_connections, so the scheduler never re-pings the same reset
window twice.
- Settings: codexAutoPing.connections map, default {} (nobody opted in),
validated by the shared Zod schema.
- Respects the existing resilience layers: skips a connection whose
provider circuit breaker is open or whose rateLimitedUntil cooldown is
active, and applies its own 15-minute failure cooldown after a failed
ping.
- UI: new per-connection toggle (Settings -> AI -> Codex Quota Auto-Ping)
with an explicit "consumes real quota" tooltip, wired to the settings
PATCH route; i18n keys added to all 43 locales (EN authored, others
filled from the EN fallback pending real translation).
- 15 new deterministic unit tests covering enable/disable, first-reset
observation (cache-only, no ping), reset-slide ping, stable-reset
no-op, min-ping-interval, same-resetKey dedupe, session/weekly quota
exhaustion, non-OAuth skip, circuit-breaker-open skip, cooldown skip,
failure-cooldown skip, failed-ping bookkeeping, the real executor call
shape, and credential-refresh failure handling.
Antigravity's 2-bucket auto-ping is out of scope for this PR (no upstream
reference exists for its reset shape) and is tracked as a follow-up.
Ref #6977 (backend + settings + minimal UI toggle; Antigravity follow-up
tracked separately — not closing the issue from this PR)
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Thanks for building out Phase 1 of #6977 — this is a clean, well-scoped implementation: strictly opt-in (default off, per-connection), fully dependency-injected/deterministic scheduler, respects the existing circuit-breaker/cooldown resilience layers, and ships 15 solid unit tests. I ran them locally (all green) along with the related db-providers-crud and settings-schema suites (41/41 green, no regression). ESLint is clean on every touched file, and the call graph (getExecutor, getCodexUsage, refreshAndUpdateCredentials, getProviderConnections) all lines up with current signatures. One blocker before merge: GitHub reports a conflict, and I scoped it with We'll resolve that i18n conflict and rebase onto the current release tip, then merge. Per the parked plan-file for #6977, the last step is the live_check on the VPS: enable auto-ping for one real Codex OAuth connection, wait for a session-quota reset, and confirm exactly one minimal ping fires (last_pinged_reset_key updates) without extra burn — will report back once that's done. Small nit for later, not blocking: the module docstring calls the ping request "non-billed-model", which reads a little inconsistent with the UI's own "consumes a small amount of real Codex quota" warning — worth aligning the wording so it doesn't undersell what's actually happening. |
…quota-auto-ping # Conflicts: # src/i18n/messages/pt-BR.json
…souzapw#6977) (diegosouzapw#6995) Codex's rolling "session" quota window only starts counting down once a request lands inside it, so an idle connection's window keeps sliding forward and the first real request after a long idle period pays for the whole warm-up latency. This adds a strictly opt-in, per-connection scheduler (default OFF) that watches an enabled connection's reported resetAt and, once it slides forward, fires one tiny non-billed-model request through the real Codex executor to keep the window warm. - New in-process scheduler (src/lib/services/quotaAutoPing.ts), fully dependency-injected (settings, DB, credential refresh, usage fetch, executor, circuit breaker) and clock-injectable for deterministic tests. Reimplemented in TS from the shipped 9router src/shared/services/quotaAutoPing.js (Codex half only). - Migration 123: last_ping_at / last_pinged_reset_key on provider_connections, so the scheduler never re-pings the same reset window twice. - Settings: codexAutoPing.connections map, default {} (nobody opted in), validated by the shared Zod schema. - Respects the existing resilience layers: skips a connection whose provider circuit breaker is open or whose rateLimitedUntil cooldown is active, and applies its own 15-minute failure cooldown after a failed ping. - UI: new per-connection toggle (Settings -> AI -> Codex Quota Auto-Ping) with an explicit "consumes real quota" tooltip, wired to the settings PATCH route; i18n keys added to all 43 locales (EN authored, others filled from the EN fallback pending real translation). - 15 new deterministic unit tests covering enable/disable, first-reset observation (cache-only, no ping), reset-slide ping, stable-reset no-op, min-ping-interval, same-resetKey dedupe, session/weekly quota exhaustion, non-OAuth skip, circuit-breaker-open skip, cooldown skip, failure-cooldown skip, failed-ping bookkeeping, the real executor call shape, and credential-refresh failure handling. Antigravity's 2-bucket auto-ping is out of scope for this PR (no upstream reference exists for its reset shape) and is tracked as a follow-up. Ref diegosouzapw#6977 (backend + settings + minimal UI toggle; Antigravity follow-up tracked separately — not closing the issue from this PR)
The opt-in Codex quota auto-ping (diegosouzapw#6977/diegosouzapw#6995) pinned its ping model to `gpt-5.1-codex-mini` in src/shared/constants/quotaAutoPing.ts. OpenAI shut that model down on 2026-07-23 and the repo's own lifecycle registry (open-sse/services/modelLifecycle.ts + config/quality/model-lifecycle.json) already rejects it on the request path, but the scheduler never consulted that gate: every window slide sent the dead id through the real executor, the failure landed in the 15-minute cooldown, and the same id was retried forever. The feature could no longer warm a window, and any future retirement of a pinned id would regress it the same way. The ping model is now resolved per tick from the provider catalog and the lifecycle registry: the first base-model entry (effort-suffixed variants are skipped because the ping sets `reasoning.effort` itself) that `isModelSelectable("codex", id)` allows, which is the same provider-scoped gate chatCore applies. The registry import stays lazy, like the executor import, because this module is on the instrumentation boot path (diegosouzapw#12074). When nothing in the catalog is selectable, the provider is paused before any throttle slot, usage read, or executor call, with one warning per state change instead of a blind retry loop. Closes diegosouzapw#11905 Co-authored-by: Leon Marcos <leonaniagomez@gmail.com>
…souzapw#6977) (diegosouzapw#6995) Codex's rolling "session" quota window only starts counting down once a request lands inside it, so an idle connection's window keeps sliding forward and the first real request after a long idle period pays for the whole warm-up latency. This adds a strictly opt-in, per-connection scheduler (default OFF) that watches an enabled connection's reported resetAt and, once it slides forward, fires one tiny non-billed-model request through the real Codex executor to keep the window warm. - New in-process scheduler (src/lib/services/quotaAutoPing.ts), fully dependency-injected (settings, DB, credential refresh, usage fetch, executor, circuit breaker) and clock-injectable for deterministic tests. Reimplemented in TS from the shipped 9router src/shared/services/quotaAutoPing.js (Codex half only). - Migration 123: last_ping_at / last_pinged_reset_key on provider_connections, so the scheduler never re-pings the same reset window twice. - Settings: codexAutoPing.connections map, default {} (nobody opted in), validated by the shared Zod schema. - Respects the existing resilience layers: skips a connection whose provider circuit breaker is open or whose rateLimitedUntil cooldown is active, and applies its own 15-minute failure cooldown after a failed ping. - UI: new per-connection toggle (Settings -> AI -> Codex Quota Auto-Ping) with an explicit "consumes real quota" tooltip, wired to the settings PATCH route; i18n keys added to all 43 locales (EN authored, others filled from the EN fallback pending real translation). - 15 new deterministic unit tests covering enable/disable, first-reset observation (cache-only, no ping), reset-slide ping, stable-reset no-op, min-ping-interval, same-resetKey dedupe, session/weekly quota exhaustion, non-OAuth skip, circuit-breaker-open skip, cooldown skip, failure-cooldown skip, failed-ping bookkeeping, the real executor call shape, and credential-refresh failure handling. Antigravity's 2-bucket auto-ping is out of scope for this PR (no upstream reference exists for its reset shape) and is tracked as a follow-up. Ref diegosouzapw#6977 (backend + settings + minimal UI toggle; Antigravity follow-up tracked separately — not closing the issue from this PR)
Summary
Ref #6977 — Codex's rolling "session" quota window only starts counting down once a request lands inside it, so an idle connection's window keeps sliding forward and the first real request after a long idle period pays for the whole warm-up latency. This adds a strictly opt-in, per-connection scheduler (default OFF) that watches an enabled connection's reported
resetAtand, once it slides forward, fires one tiny non-billed-model request through the real Codex executor to keep the window warm.src/lib/services/quotaAutoPing.ts, fully dependency-injected (settings, DB, credential refresh, usage fetch, executor, circuit breaker) and clock-injectable for deterministic tests. Reimplemented in TS from the shipped 9routersrc/shared/services/quotaAutoPing.js(Codex half only — Antigravity has no upstream reference for its 2-bucket reset shape).last_ping_at/last_pinged_reset_keyonprovider_connections, so the scheduler never re-pings the same reset window twice.codexAutoPing.connectionsmap, default{}(nobody opted in — every ping burns a small amount of real quota), validated by the shared Zod schema (src/shared/validation/settingsSchemas.ts).getCircuitBreaker("codex").canExecute()) or whoserateLimitedUntilcooldown is active, and applies its own 15-minute failure cooldown after a failed ping.src/instrumentation-node.ts, inside the existing background-services gate) alongside the other schedulers.codexAutoPingWarningtooltip ("consumes real quota"). Lists OAuth Codex connections from/api/providersand PATCHes/api/settings. i18n keys added to all 43 locales (EN authored by hand; others filled from the EN fallback string as a starting point, pending real translation via the normal i18n pipeline).Scope note (Phase 1 of #6977)
This PR ships the Codex half only, per the analyzed plan. Antigravity auto-ping (2 quota buckets, no shipped upstream reference) is intentionally out of scope and tracked as a follow-up against #6977 — not closing the issue from this PR.
Test plan
node:testunit tests (tests/unit/quota-auto-ping.test.ts): setting absent (no-op), firstresetAtobservation (cache-only, no ping), reset-slide ping, stable-reset no-op, min-ping-interval dedupe, same-resetKeydedupe across clock drift, session-quota-exhausted skip, blocking-quota-exhausted skip, non-OAuth skip, circuit-breaker-open skip, connection-cooldown skip, failure-cooldown skip, failed-ping bookkeeping (no DB write, failure cached), the real executor call shape (model/body/credentials), and credential-refresh-failure handling.npm run typecheck:core— cleannpx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>— 0 errors, 0 warningsnode scripts/check/check-file-size.mjs— no new/growing violations (3 pre-existing base-red violations on this branch tip are unrelated files I never touched:ProxyRegistryManager.tsx,tokenHealthCheck.ts,open-sse/utils/stream.ts)node scripts/check/check-complexity.mjs— 2056 == baseline 2056 (clean after extractingisPingCandidateBlocked/shouldSendPing/refreshConnectionForPing/pingProviderConnectionshelpers to keep every function ≤15 cyclomatic complexity)node scripts/check/check-cognitive-complexity.mjs— 890 == baseline 890node scripts/check/check-changelog-integrity.mjs— OK, no base bullets lost vsorigin/release/v3.8.47node --import tsx/esm --test tests/unit/quota-auto-ping.test.ts tests/unit/db-providers-crud.test.ts tests/unit/settings-transform-schema.test.ts tests/unit/settings-schema-routing-strategies.test.ts— all green, confirms the newprovider_connectionscolumns and settings schema entry don't regress existing CRUD/settings coverage