fix(rotation): persist cooldown when account has no resolvable accountId - #608
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Warning Review limit reached
More reviews will be available in 23 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough
ChangesAuth-failure cooldown persistence
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~5 minutes Possibly related PRs
a few observations as a senior eye on this change: the fix is correct and minimal. test coverage is solid but narrow. concurrency risk not introduced here but worth noting. windows edge case. disk-write timing on windows under heavy i/o can cause the debounced write to be delayed or dropped at process exit. no test covers a simulated-restart scenario (write → process exit → reload) — that's the actual bug being fixed. consider an integration-level test or at least a comment in no public api changes. confirmed — no exported types or signatures changed. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
Address Greptile P2 on #608: pin the end-to-end exhaustion behavior by asserting the single-account pool returns 503 SERVICE_UNAVAILABLE, not just that no upstream call was made and the cooldown persisted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The missing-accountId branch in the runtime rotation loop marked the account cooling down (`markAccountCoolingDown`) but was the lone cooldown branch that never called `saveToDiskDebounced()` — every other branch (network-error, 429, server-error, 401 invalidation) persists its mutation. Because `coolingDownUntil`/`cooldownReason` are serialized in the V3 snapshot, a process restart inside the 30s cooldown window dropped the cooldown and immediately re-selected the still-broken account. Impact is low (no upstream request is issued and rotation proceeds gracefully in-memory), but the inconsistency is a real durability gap. Add the missing `saveToDiskDebounced()` so the branch matches the others and the cooldown survives a restart. Found via a codebase-wide bug-hunt sweep. Regression test asserts the branch fires (account with no stored accountId + non-JWT access token) and persists; it fails without the fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Greptile P2 on #608: pin the end-to-end exhaustion behavior by asserting the single-account pool returns 503 SERVICE_UNAVAILABLE, not just that no upstream call was made and the cooldown persisted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c660596 to
0f50e3f
Compare
The short-retry branch of the runtime fetch loop in index.ts marks the account rate-limited via `markRateLimitedWithReason` (which mutates the disk-serialized `rateLimitResetTimes`) and then sleeps + retries, but never called `saveToDiskDebounced()` — unlike the sibling full-rotation branch directly below it, which persists at line ~2327. A crash during the retry sleep (or before any later save) lost the rate-limit reset time; on restart the account was immediately re-selected, defeating the cooldown. This is the same durability gap class as PR #608 (runtime-rotation-proxy.ts) and PR #607, in a third location. Add the missing `saveToDiskDebounced()` after `recordRateLimit()` in the short-retry branch, mirroring the full-rotation branch. Found by a pre-release deep stress-test sweep. Regression test drives a 429 with a sub-threshold cooldown into the short-retry path and asserts the save is scheduled; it fails without the fix (verified by mutation). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promote the 2.3.0-beta line to stable and ship three runtime-rotation durability fixes landed after beta.3: - #607: break stale-recovery deadlock on persisted transient account state (fixes #606) - #608: persist cooldown when an account has no resolvable accountId - #609: persist rate-limit window in the short-retry 429 path Version-coupled manifests bumped 2.3.0-beta.3 -> 2.3.0 (package.json, package-lock.json, .codex-plugin/plugin.json, AGENTS.md), release portal links updated in README.md and docs/README.md (v2.3.0 current stable, beta.3/beta.2 demoted to prior prerelease), CHANGELOG entry added, and docs/releases/v2.3.0.md created. documentation.test.ts coupling assertions green. Full suite 4920 pass / 3 skip / 0 fail; lint + tsc clean; pack budget ok (codex-multi-auth@2.3.0, 1062597 bytes / 1201 files). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
runRotationLoopmarks the account cooling down viamarkAccountCoolingDownbut was the lone cooldown branch that never calledsaveToDiskDebounced(). Every other cooldown branch in the same function persists its mutation: network-error, 429, server-error, and 401 invalidation.coolingDownUntil/cooldownReasonare serialized in the V3 storage snapshot, a process restart inside the 30s cooldown window dropped the cooldown and immediately re-selected the still-broken account.What Changed
lib/runtime-rotation-proxy.ts— add the missingaccountManager.saveToDiskDebounced()aftermarkAccountCoolingDownin the missing-accountId branch, mirroring the network-error branch directly below it. Added a comment explaining the persistence requirement.test/runtime-rotation-proxy.test.ts— regression test: an account with no storedaccountIdand a non-JWT access token drivesresolveAccountIdto return null, the branch fires, no upstream request is issued, and the cooldown is persisted. The test fails without the fix (verified by mutation).Severity
Low. No upstream request is issued (token refresh already succeeded), the cooldown is only 30s, and a restart within the window degrades gracefully — the account is simply re-selected, re-cooled in memory, and rotation proceeds to the next account. This is a consistency/durability gap, not a cascading failure. Fixing it makes the branch consistent with every other cooldown path.
Validation
npm run lintnpm run typechecknpm test(4911 passed, 3 skipped, 0 failures)npm test -- test/documentation.test.tsnpm run buildDocs and Governance Checklist
docs/getting-started.mdupdated (if onboarding flow changed)docs/features.mdupdated (if capability surface changed)docs/reference/*pages updated (if commands/settings/paths changed)docs/upgrade.mdupdated (if migration behavior changed)SECURITY.mdandCONTRIBUTING.mdreviewed for alignmentNo user-visible surface changed — internal rotation-loop consistency fix.
Risk and Rollback
🤖 Generated with Claude Code
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
patches the one cooldown branch in
runRotationLoopthat mutated cooldown state without scheduling a disk write. every other branch (network-error, 429, server-error, 401 invalidation) already callssaveToDiskDebounced(); the no-accountId branch did not, so a process restart inside the 30s window dropped the cooldown and could immediately re-select the still-broken account.lib/runtime-rotation-proxy.ts— addsaccountManager.saveToDiskDebounced()aftermarkAccountCoolingDownin the missing-accountId branch, with a comment explaining the V3 serialization requirement.test/runtime-rotation-proxy.test.ts— adds a regression test that confirms no upstream call is issued, the account receives the expected cooldown fields, the response is 503, andsaveToDiskDebouncedis called.Confidence Score: 5/5
safe to merge — one-line addition on an already-degraded path that previously skipped a debounced disk write, matching the established pattern of every sibling cooldown branch
the change is minimal and surgical: one saveToDiskDebounced() call added to close a consistency gap between the no-accountId branch and its three sibling branches; no new code paths, no new state, no behavioral change except durability of the cooldown across restarts; regression test correctly exercises the branch end-to-end and would have failed before the fix
no files require special attention
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[runRotationLoop iteration] --> B[ensureFreshAccessToken] B -->|not ok, invalidated| C[return 401 to client] B -->|not ok, retryable| D[transientAttempts++, continue] B -->|ok| E[resolveAccountId] E -->|null: no accountId| F[markAccountCoolingDown\nauthFailureCooldownMs] F --> G["saveToDiskDebounced() ✅ ADDED"] G --> H[transientAttempts++, continue] E -->|resolved| I[send upstream request] I -->|network error| J[markAccountCoolingDown\nnetworkErrorCooldownMs] J --> K[saveToDiskDebounced] I -->|429| L[markRateLimitedWithReason] L --> M[saveToDiskDebounced] I -->|5xx| N[markAccountCoolingDown\nserverErrorCooldownMs] N --> O[saveToDiskDebounced] I -->|success| P[stream response to client]Reviews (3): Last reviewed commit: "test(rotation): assert exhaustion status..." | Re-trigger Greptile