Skip to content

feat(rotation): add rotationStrategy selector and codex-warm tool (#183, #182)#184

Merged
ndycode merged 1 commit into
mainfrom
feat/rotation-strategy-and-warm
Jun 30, 2026
Merged

feat(rotation): add rotationStrategy selector and codex-warm tool (#183, #182)#184
ndycode merged 1 commit into
mainfrom
feat/rotation-strategy-and-warm

Conversation

@ndycode

@ndycode ndycode commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

Implements two requested account-management features plus an availability fix surfaced during adversarial review.

#183rotationStrategy config (env CODEX_AUTH_ROTATION_STRATEGY)

  • hybrid (default, unchanged): stick to the current account while healthy, else score-select (health + tokens + freshness, which spreads load).
  • sticky: drain-first — stay on one account until it's rate-limited/cooling down, then pick the lowest-indexed available account so load concentrates and the other accounts keep their quota windows in reserve. This is the direct fix for the issue's "all accounts share an initiation time → all hit weekly cooldown together" problem under round-robin.
  • round-robin: advance through accounts in order.

Routed through the request hot path via a new AccountManager.getAccountForStrategy dispatcher. Default hybrid is behavior-identical to the prior code → zero regression.

#182codex-warm tool

Sends one minimal billable POST /codex/responses per enabled account so each account's rolling ~5h usage window actually starts at session start, instead of only when rotation eventually lands on it ("send a hi to each account to open all windows").

Note: a read-only GET /wham/usage does not open the window (it only reports server-side windows that already exist), so warming must send a real inference request. The ping is deliberately tiny (reasoning effort none, verbosity low, no stored conversation) to keep quota cost negligible.

Architecture: pure orchestrator (lib/accounts/warm.ts, fully unit-tested) + isolated network adapter (lib/accounts/warm-request.ts). Disabled accounts are skipped; per-account failures are reported without aborting the batch. Complements the existing codex-switch index=N (which already switches the active account on demand).

fix(rotation): local token-bucket depletion no longer strands sticky/hybrid

Adversarial review found that when an account's local client-side token bucket is depleted, the request loop drained it and continued — but sticky/hybrid re-pinned the same account (their availability check ignores the local bucket), so the attempted-set guard broke the loop and returned a spurious 503 while other accounts still had quota. Now a short auto-expiring rate-limit window (LOCAL_TOKEN_DEPLETION_COOLDOWN_MS) is applied on depletion so every strategy advances, and getMinWaitTimeForFamily can engage the wait path when the whole pool is depleted.

Release hygiene

  • Synced .codex-plugin/plugin.json version (was drifted 6.3.3 vs package.json 6.3.4), fixing a pre-existing doc-parity failure on main.
  • Added release-please extra-files so plugin.json tracks package.json automatically on future releases.

Testing

  • 41 feature unit tests + 19 adversarial chaos stress tests (flapping rate-limits, mid-rotation removal/reindex, disable churn, strategy switching, restart, 200-account fleets, all-fail/throw/skip mixes, real /responses adapter with injected fetch covering 2xx/429/4xx/5xx/network/timeout).
  • Full suite: 2606 passed, 1 skipped, 0 failed. Typecheck, ESLint, and build all clean.
  • doc-parity and plugin-config snapshots updated for the new tool (22 tools) and config key.

Closes #183
Closes #182

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new codex-warm command to warm up all configured accounts and report per-account results.
    • Added a new account rotation option with hybrid, sticky, and round-robin behavior, configurable via settings or environment variable.
  • Bug Fixes

    • Improved account handling when an account runs out of local tokens, making it temporarily ineligible and helping rotation recover more smoothly.
  • Chores

    • Updated the plugin version to 6.3.4 and refreshed related documentation/version tracking.

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

adds rotationStrategy config (hybrid/sticky/round-robin) for account load-balancing, the codex-warm tool that primes every account's usage window at session start, and a local-token-depletion fix that prevents sticky/hybrid from re-pinning the same depleted account and returning a spurious 503.

  • rotation strategygetAccountForStrategy dispatcher routes to sticky (drain-first, lowest-indexed fallback), round-robin, or the pre-existing hybrid; default is hybrid so existing behavior is unchanged; env override CODEX_AUTH_ROTATION_STRATEGY wins over config; chaos tests cover 200-account fleets, mid-rotation removal/reindex, strategy-switching mid-session, and simulated restarts.
  • codex-warm tool — pure orchestrator (warm.ts) + isolated network adapter (warm-request.ts) send one minimal POST /codex/responses per enabled account concurrently; failures are caught per-account and never abort the batch; 429 is treated as a successful warm since the window is already ticking.
  • depletion fixmarkRateLimitedWithReason with LOCAL_TOKEN_DEPLETION_COOLDOWN_MS (10 s) is applied when the local token bucket is empty, making the account ineligible in isRateLimitedForFamily so every rotation strategy advances instead of looping on the same account until the attempted guard fires a 503.

Confidence Score: 3/5

the rotation selector and depletion fix are safe to merge; the warm tool's concurrent token-refresh path can silently lose a refreshed credential when two accounts' tokens expire simultaneously.

the rotation strategy wiring, sticky selector, and depletion fix are well-designed and backed by strong chaos tests. the warm tool's default concurrent mode runs ensureCodexUsageAccessToken in parallel for all accounts — if two accounts hit the refresh branch at the same time, their concurrent persistRefreshedCredentials writes can race at the file level, leaving one account's new token silently overwritten. this is a real failure mode when users add all accounts at the same session and run codex-warm immediately (all tokens expire together). the hardcoded WARM_MODEL and missing timeout test are lower-stakes but worth fixing.

lib/tools/codex-warm.ts (concurrent refresh race) and test/accounts-warm-request.test.ts (timeout path not exercised)

Important Files Changed

Filename Overview
lib/tools/codex-warm.ts orchestrates the warm flow; concurrent default + ensureCodexUsageAccessToken can race on persistRefreshedCredentials when multiple accounts need simultaneous token refresh
lib/accounts/warm-request.ts new network adapter for the warm ping; well-structured with injected fetch and timeout, but WARM_MODEL is hardcoded and the timeout/abort path has no test coverage
lib/accounts/warm.ts pure orchestrator — iteration, skip logic, and error capture are cleanly separated from i/o; well tested including concurrent ordering and thrown-error capture
lib/accounts/rotation.ts adds drain-first sticky selector with correct lowest-index fallback, lastUsed update, and bounds checks; chaos tests cover reindex-after-removal and all-exhausted paths
lib/accounts.ts adds getAccountForStrategy dispatcher and getCurrentOrNextForFamilySticky shim; switch is exhaustive with default covering hybrid — zero regression to existing callers
index.ts wires rotationStrategy into the request hot path and adds LOCAL_TOKEN_DEPLETION_COOLDOWN_MS markRateLimitedWithReason fix; the double recordRateLimit + markRateLimitedWithReason sequence deserves a comment on whether the shorter 10s window can overwrite a longer server-set cooldown
lib/config.ts adds getRotationStrategy with env override, config fallback, and bogus-value graceful degradation; ROTATION_STRATEGIES Set cast is safe
lib/constants.ts adds LOCAL_TOKEN_DEPLETION_COOLDOWN_MS = 10_000 with clear comment tying it to one token refill period at 6 tokens/min
test/chaos/rotation-strategy-stress.test.ts comprehensive adversarial coverage: 200-account fleet, mid-rotation removal/reindex, disable churn, strategy switching, simulated restart — catches positional-index corruption bugs

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant CodexWarmTool
    participant warmAccounts
    participant createWarmOne
    participant ensureCodexUsageAccessToken
    participant warmAccountWindow
    participant Upstream

    User->>CodexWarmTool: execute codex-warm
    CodexWarmTool->>warmAccounts: "accounts[], warmOne (concurrent=true)"
    par for each enabled account
        warmAccounts->>createWarmOne: warmOne(account, index)
        createWarmOne->>ensureCodexUsageAccessToken: refresh token if near expiry
        Note over ensureCodexUsageAccessToken: concurrent refreshes can race on persistRefreshedCredentials
        ensureCodexUsageAccessToken-->>createWarmOne: accessToken
        createWarmOne->>warmAccountWindow: POST /codex/responses (gpt-5.4)
        warmAccountWindow->>Upstream: "minimal billable ping (store=false)"
        Upstream-->>warmAccountWindow: "2xx / 429 = warmed, other = throws"
        warmAccountWindow-->>createWarmOne: true
        createWarmOne-->>warmAccounts: status warmed
    end
    warmAccounts-->>CodexWarmTool: WarmSummary
    CodexWarmTool-->>User: N warmed, M failed, K skipped
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant CodexWarmTool
    participant warmAccounts
    participant createWarmOne
    participant ensureCodexUsageAccessToken
    participant warmAccountWindow
    participant Upstream

    User->>CodexWarmTool: execute codex-warm
    CodexWarmTool->>warmAccounts: "accounts[], warmOne (concurrent=true)"
    par for each enabled account
        warmAccounts->>createWarmOne: warmOne(account, index)
        createWarmOne->>ensureCodexUsageAccessToken: refresh token if near expiry
        Note over ensureCodexUsageAccessToken: concurrent refreshes can race on persistRefreshedCredentials
        ensureCodexUsageAccessToken-->>createWarmOne: accessToken
        createWarmOne->>warmAccountWindow: POST /codex/responses (gpt-5.4)
        warmAccountWindow->>Upstream: "minimal billable ping (store=false)"
        Upstream-->>warmAccountWindow: "2xx / 429 = warmed, other = throws"
        warmAccountWindow-->>createWarmOne: true
        createWarmOne-->>warmAccounts: status warmed
    end
    warmAccounts-->>CodexWarmTool: WarmSummary
    CodexWarmTool-->>User: N warmed, M failed, K skipped
Loading

Fix All in Codex

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
lib/tools/codex-warm.ts:44-57
**concurrent token-refresh race on `persistRefreshedCredentials`**

`warmAccounts` defaults to `concurrent: true`, so `createWarmOne` is invoked via `Promise.all` for every enabled account simultaneously. when any of those accounts has a near-expiry token, `ensureCodexUsageAccessToken` proceeds past the early-return guard and eventually calls `persistRefreshedCredentials`, which presumably does a read-modify-write of the shared storage file. two simultaneous refreshes for two different accounts can interleave as: (1) A reads file, (2) B reads file, (3) A writes — account 0 refreshed, account 1 stale, (4) B writes — account 1 refreshed, but the account 0 refresh from step 3 is now overwritten. net effect: one account's new token is silently lost and the next request for it re-enters the refresh path or fails auth.

### Issue 2 of 3
test/accounts-warm-request.test.ts:1-20
**missing vitest coverage for the timeout/abort path**

`warmAccountWindow` exposes `timeoutMs` specifically to make the abort path testable, but no test exercises it. the `AbortController.abort()` callback inside `setTimeout` is completely untested — a broken timeout (e.g. `clearTimeout` accidentally before the request, or signal never wired to fetch) would go undetected. add a test that passes a very short `timeoutMs` with a `fetchImpl` that never resolves, and asserts that the call rejects with an abort error.

### Issue 3 of 3
lib/accounts/warm-request.ts:26
**hardcoded `WARM_MODEL` will silently fail for accounts without gpt-5.4 access**

the warm ping is always sent as `gpt-5.4`. an account that only has access to a different codex model (or is on a plan that doesn't include gpt-5.4) will receive a 4xx, which `warmAccountWindow` throws on, and `warmAccounts` records as `failed`. the account's window is never opened, but the user sees "Failed - HTTP 403" without any signal that the model is wrong rather than the account. since `getCodexInstructions(model)` already accepts a model parameter, consider at minimum exposing `warmModel` as a configurable parameter.

Reviews (1): Last reviewed commit: "feat(rotation): add rotationStrategy sel..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Adds two account-management features plus a related availability fix.

#183 — rotationStrategy config (env CODEX_AUTH_ROTATION_STRATEGY):
- hybrid (default, unchanged): stick to the current account while healthy,
  else score-select (health+tokens+freshness, which spreads load).
- sticky: drain-first — stay on one account until it is rate-limited/
  cooling down, then pick the lowest-indexed available account so load
  concentrates and the other accounts keep their quota windows in reserve.
  Directly addresses weekly-quota cooldowns landing on every account at once.
- round-robin: advance through accounts in order.
Wired through the request hot path via AccountManager.getAccountForStrategy.

#182 — codex-warm tool:
- Sends one minimal billable POST /codex/responses per enabled account so
  the rolling ~5h usage window actually starts at session start, instead of
  only when rotation lands on each account. (A read-only GET /wham/usage does
  NOT open the window, so warming must send a real inference request.)
- Pure orchestrator (lib/accounts/warm.ts) + isolated network adapter
  (lib/accounts/warm-request.ts); disabled accounts skipped, per-account
  failures reported without aborting the batch. Complements codex-switch.

fix(rotation): rotate off an account whose LOCAL token bucket is depleted.
Previously the request loop drained the bucket and continue'd, but sticky/
hybrid re-pinned the same account (their availability check ignores the local
bucket); the attempted-set guard then 503'd while other accounts had quota.
Now a short auto-expiring rate-limit window (LOCAL_TOKEN_DEPLETION_COOLDOWN_MS)
is applied so every strategy advances and getMinWaitTimeForFamily can wait.

Also: sync .codex-plugin/plugin.json version (was drifted 6.3.3 vs 6.3.4) and
add release-please extra-files so plugin.json tracks package.json automatically.

Tests: 41 feature tests + 19 adversarial chaos stress tests; doc-parity and
plugin-config snapshots updated for the new tool and config key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5af3e90c-d324-4884-80d5-50191ffe7c02

📥 Commits

Reviewing files that changed from the base of the PR and between e5f7bbb and 2925997.

📒 Files selected for processing (27)
  • .codex-plugin/plugin.json
  • CHANGELOG.md
  • README.md
  • docs/development/ARCHITECTURE.md
  • index.ts
  • lib/accounts.ts
  • lib/accounts/rotation.ts
  • lib/accounts/warm-request.ts
  • lib/accounts/warm.ts
  • lib/config.ts
  • lib/constants.ts
  • lib/schemas.ts
  • lib/tools/AGENTS.md
  • lib/tools/codex-help.ts
  • lib/tools/codex-warm.ts
  • lib/tools/index.ts
  • release-please-config.json
  • test/accounts-warm-request.test.ts
  • test/accounts-warm.test.ts
  • test/chaos/accounts-warm-stress.test.ts
  • test/chaos/rotation-strategy-stress.test.ts
  • test/doc-parity.test.ts
  • test/index-retry.test.ts
  • test/index.test.ts
  • test/plugin-config.test.ts
  • test/rotation-strategy.test.ts
  • test/tools-codex-warm.test.ts

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting


📝 Walkthrough

Walkthrough

Adds two features: (1) a configurable rotationStrategy (hybrid/sticky/round-robin) read from config or CODEX_AUTH_ROTATION_STRATEGY env var, with a new sticky drain-first selection algorithm in AccountRotation and token-depletion cooldown in the request traversal loop; (2) a codex-warm tool that sends a lightweight /codex/responses probe to prime each enabled account's quota window.

Changes

Rotation Strategy Configuration

Layer / File(s) Summary
RotationStrategy type, schema, and config resolver
lib/schemas.ts, lib/config.ts, lib/constants.ts
PluginConfigSchema gains an optional rotationStrategy enum field; lib/config.ts exports RotationStrategy type, ROTATION_STRATEGIES set, and getRotationStrategy() resolver (env → config → "hybrid" default); ACCOUNT_LIMITS gains LOCAL_TOKEN_DEPLETION_COOLDOWN_MS = 10_000.
Sticky selection algorithm in AccountRotation and AccountManager
lib/accounts/rotation.ts, lib/accounts.ts
AccountRotation.getCurrentOrNextForFamilySticky implements drain-first selection; AccountManager delegates it and adds getAccountForStrategy() dispatcher switching on strategy.
Strategy wiring in request traversal and token-depletion cooldown
index.ts
Imports getRotationStrategy, computes rotationStrategy at loader init, replaces getCurrentOrNextForFamilyHybrid call with getAccountForStrategy, and marks depleted token-bucket accounts rate-limited for LOCAL_TOKEN_DEPLETION_COOLDOWN_MS.
Rotation strategy tests
test/rotation-strategy.test.ts, test/chaos/rotation-strategy-stress.test.ts, test/plugin-config.test.ts, test/index*.test.ts
Unit tests for getRotationStrategy config/env resolution, sticky selection semantics, and strategy dispatcher; adversarial stress tests across all three strategies; mock updates for getAccountForStrategy in existing test suites.

codex-warm Tool

Layer / File(s) Summary
warm-request HTTP probe and warmAccounts orchestrator
lib/accounts/warm-request.ts, lib/accounts/warm.ts
warmAccountWindow sends a minimal POST /codex/responses with abort timeout, treating 2xx/429 as success; warmAccounts orchestrator runs warmOne per eligible account concurrently or sequentially, captures failures, and returns a WarmSummary.
codex-warm tool definition and registry registration
lib/tools/codex-warm.ts, lib/tools/index.ts, lib/tools/codex-help.ts
createWarmOne chains token refresh → account-id resolution → warmAccountWindow; createCodexWarmTool loads storage, runs warmAccounts, formats per-account output; registered under "codex-warm" in the tool registry.
codex-warm and warmAccounts tests
test/accounts-warm-request.test.ts, test/accounts-warm.test.ts, test/chaos/accounts-warm-stress.test.ts, test/tools-codex-warm.test.ts
Tests for buildWarmRequestBody, warmAccountWindow (HTTP behavior, 429 success, error propagation), warmAccounts orchestrator (concurrency, ordering, failure capture), and the full codex-warm tool including createWarmOne adapter.

Docs, Parity Tests, and Release Config

Layer / File(s) Summary
Docs, parity, version bump, release config
docs/development/ARCHITECTURE.md, lib/tools/AGENTS.md, README.md, CHANGELOG.md, test/doc-parity.test.ts, .codex-plugin/plugin.json, release-please-config.json
Tool counts updated from 21 to 22 across docs and parity tests; codex-warm added to README and CHANGELOG; plugin manifest bumped to 6.3.4; plugin.json added to release-please extra-files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ndycode/oc-codex-multi-auth#165: Both PRs modify the per-request account traversal in index.ts when the local token bucket is depleted—this PR adds a cooldown window via markRateLimitedWithReason, while #165 changed the stop-vs-rotate behavior in the same code path.

Poem

🐇 Hop hop, the accounts all warm up in line,
Sticky or round-robin, the quotas align!
Each window primed with a featherweight call,
No account left cold, no rotation to stall.
The rabbit declares: codex-warm for all! 🌟

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rotation-strategy-and-warm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ndycode ndycode merged commit 38480e9 into main Jun 30, 2026
1 of 2 checks passed
Comment thread lib/tools/codex-warm.ts
Comment on lines +44 to +57
export function createWarmOne(
storage: AccountStorageV3,
): (account: AccountMetadataV3) => Promise<WarmOutcome> {
return async (account: AccountMetadataV3): Promise<WarmOutcome> => {
const { accessToken } = await ensureCodexUsageAccessToken({ storage, account });
const accountId = resolveCodexUsageAccountId({ account, accessToken });
if (!accountId) {
return {
status: "failed",
detail: "could not resolve account id (re-login may be required)",
};
}
await warmAccountWindow({
accountId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 concurrent token-refresh race on persistRefreshedCredentials

warmAccounts defaults to concurrent: true, so createWarmOne is invoked via Promise.all for every enabled account simultaneously. when any of those accounts has a near-expiry token, ensureCodexUsageAccessToken proceeds past the early-return guard and eventually calls persistRefreshedCredentials, which presumably does a read-modify-write of the shared storage file. two simultaneous refreshes for two different accounts can interleave as: (1) A reads file, (2) B reads file, (3) A writes — account 0 refreshed, account 1 stale, (4) B writes — account 1 refreshed, but the account 0 refresh from step 3 is now overwritten. net effect: one account's new token is silently lost and the next request for it re-enters the refresh path or fails auth. does persistRefreshedCredentials do an atomic in-place update per account, or does it read the full storage file and overwrite? if the latter, concurrent warm calls for multiple expiring accounts will race.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/tools/codex-warm.ts
Line: 44-57

Comment:
**concurrent token-refresh race on `persistRefreshedCredentials`**

`warmAccounts` defaults to `concurrent: true`, so `createWarmOne` is invoked via `Promise.all` for every enabled account simultaneously. when any of those accounts has a near-expiry token, `ensureCodexUsageAccessToken` proceeds past the early-return guard and eventually calls `persistRefreshedCredentials`, which presumably does a read-modify-write of the shared storage file. two simultaneous refreshes for two different accounts can interleave as: (1) A reads file, (2) B reads file, (3) A writes — account 0 refreshed, account 1 stale, (4) B writes — account 1 refreshed, but the account 0 refresh from step 3 is now overwritten. net effect: one account's new token is silently lost and the next request for it re-enters the refresh path or fails auth. does `persistRefreshedCredentials` do an atomic in-place update per account, or does it read the full storage file and overwrite? if the latter, concurrent warm calls for multiple expiring accounts will race.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Comment on lines +1 to +20
import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("../lib/prompts/codex.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../lib/prompts/codex.js")>();
return {
...actual,
getCodexInstructions: vi.fn(async () => "test-instructions"),
};
});

import {
buildWarmRequestBody,
warmAccountWindow,
} from "../lib/accounts/warm-request.js";
import { CODEX_BASE_URL } from "../lib/constants.js";

function fakeResponse(status: number): Response {
return {
ok: status >= 200 && status < 300,
status,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 missing vitest coverage for the timeout/abort path

warmAccountWindow exposes timeoutMs specifically to make the abort path testable, but no test exercises it. the AbortController.abort() callback inside setTimeout is completely untested — a broken timeout (e.g. clearTimeout accidentally before the request, or signal never wired to fetch) would go undetected. add a test that passes a very short timeoutMs with a fetchImpl that never resolves, and asserts that the call rejects with an abort error.

Prompt To Fix With AI
This is a comment left during a code review.
Path: test/accounts-warm-request.test.ts
Line: 1-20

Comment:
**missing vitest coverage for the timeout/abort path**

`warmAccountWindow` exposes `timeoutMs` specifically to make the abort path testable, but no test exercises it. the `AbortController.abort()` callback inside `setTimeout` is completely untested — a broken timeout (e.g. `clearTimeout` accidentally before the request, or signal never wired to fetch) would go undetected. add a test that passes a very short `timeoutMs` with a `fetchImpl` that never resolves, and asserts that the call rejects with an abort error.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

const log = createLogger("warm-request");

/** Model used for the warm ping. Mirrors the live quota-probe default. */
const WARM_MODEL = "gpt-5.4";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 hardcoded WARM_MODEL will silently fail for accounts without gpt-5.4 access

the warm ping is always sent as gpt-5.4. an account that only has access to a different codex model (or is on a plan that doesn't include gpt-5.4) will receive a 4xx, which warmAccountWindow throws on, and warmAccounts records as failed. the account's window is never opened, but the user sees "Failed - HTTP 403" without any signal that the model is wrong rather than the account. since getCodexInstructions(model) already accepts a model parameter, consider at minimum exposing warmModel as a configurable parameter.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/accounts/warm-request.ts
Line: 26

Comment:
**hardcoded `WARM_MODEL` will silently fail for accounts without gpt-5.4 access**

the warm ping is always sent as `gpt-5.4`. an account that only has access to a different codex model (or is on a plan that doesn't include gpt-5.4) will receive a 4xx, which `warmAccountWindow` throws on, and `warmAccounts` records as `failed`. the account's window is never opened, but the user sees "Failed - HTTP 403" without any signal that the model is wrong rather than the account. since `getCodexInstructions(model)` already accepts a model parameter, consider at minimum exposing `warmModel` as a configurable parameter.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Load Balancing Algorithm - Sticky [FEATURE] Being able to switch accounts manually

1 participant