Skip to content

Fix runtime rotation pool diagnostics - #480

Merged
ndycode merged 4 commits into
mainfrom
fix/issue-479-runtime-pool-diagnostics
May 11, 2026
Merged

Fix runtime rotation pool diagnostics#480
ndycode merged 4 commits into
mainfrom
fix/issue-479-runtime-pool-diagnostics

Conversation

@ndycode

@ndycode ndycode commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • add runtime pool exhaustion diagnostics with per-account skip reasons, persisted reset/reload metadata, and clearer 503 hints
  • recover stale no-account runtime divergence by resetting volatile runtime state, serializing disk reload recovery, pinning each request to a consistent AccountManager, and retrying selection while preserving pinned-account hard-fail behavior
  • align forecast/report/doctor with runtime skip diagnostics and quota-exhausted cache state; add rotation reset-runtime --json recovery command
  • address PR review follow-ups for runtime reload concurrency, pool-size recompute after reload, forecast/report/doctor regressions, and reset-runtime error/no-helper coverage

Fixes #479

Validation

  • npm run typecheck
  • npm run lint
  • npx vitest run test/codex-manager-rotation-command.test.ts test/runtime-rotation-proxy.test.ts test/forecast.test.ts test/codex-manager-report-command.test.ts test/codex-manager-forecast-command.test.ts test/repair-commands.test.ts (150 tests passed)
  • npm test (267 files, 4000 tests passed)
  • npm run build
  • npm run pack:check (Pack budget ok: 970854 bytes across 1088 files)

Risk notes

  • Runtime recovery is limited to one unpinned no-account retry when there is no explicit policy block, cooldown, or rate-limit wait.
  • Pinned unavailable accounts still return codex_pinned_account_unavailable.
  • New 503/report fields are additive JSON diagnostics.

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 runtime rotation pool exhaustion diagnostics with per-account skip reasons, stale-runtime recovery via a promise-deduplicated recoverStaleRuntimeState closure, and persisted reset/reload metadata. forecast, report, and doctor are extended to consume the new runtime overlay, and a rotation reset-runtime subcommand is added for manual recovery.

  • recoverStaleRuntimeState (runtime-rotation-proxy.ts): uses a module-scoped promise (staleRuntimeReloadPromise) to deduplicate concurrent reload attempts and a 1 s dedup window after success; reloadedAfterNoAccount per-request guard caps recovery to one retry per request. the previous concurrent-reload race is addressed.
  • knownAccountManagers flush-on-close: correctly flushes all AccountManager instances (original + any reloaded) on proxy close.
  • forecast overlay: quota-cache exhausted and runtime skip-reason signals now fold into evaluateForecastAccount availability, guarded by appendWaitReason's <= 0 guard so zero-wait entries don't emit spurious wait strings.

Confidence Score: 5/5

safe to merge; all changed paths are additive diagnostics or bounded recovery logic with per-request guards preventing infinite loops

the concurrent reload race that existed before this PR is resolved by promise deduplication; the per-request reloadedAfterNoAccount flag prevents retry loops; knownAccountManagers flush-on-close prevents data loss on proxy shutdown after a reload; new snapshot fields are additive and backward-compatible; the two remaining nits do not affect correctness

lib/runtime-rotation-proxy.ts — the chooseAccount preferred-account else-block indentation and the missing lastStaleRuntimeReloadAt update on loadFromDisk failure are worth a quick look before merge

Important Files Changed

Filename Overview
lib/runtime-rotation-proxy.ts adds per-account skip-reason collection, stale-runtime recovery via recoverStaleRuntimeState (promise-deduplicated, 1 s cooldown), policyBlockedIndexes snapshot on every request, and knownAccountManagers flush-on-close; indentation bug in preferred-account else branch and missing failure cooldown in recoverStaleRuntimeState
lib/forecast.ts adds quota-cache exhausted downgrade (ready→delayed) and runtime overlay skip-reason/policy-block check; appendWaitReason correctly guards against 0 ms, logic is sound
lib/accounts.ts adds getAccountRuntimeSkipReason (diagnostic) and static resetVolatileRuntimeState; straightforward delegation to existing tracker/circuit-breaker reset APIs
lib/runtime/runtime-observability.ts extends snapshot schema with pool-exhaustion, reset, and reload metadata; adds recordRuntimePoolExhaustion/Reset/Reload helpers; normalization handles missing fields correctly
lib/codex-manager/commands/rotation.ts adds reset-runtime subcommand; AccountManager.resetVolatileRuntimeState() is a no-op in the CLI process but recordRuntimeReset correctly persists the recovery signal cross-process; app unbind/bind path is the real in-process reset mechanism

Fix All in Codex

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

---

### Issue 1 of 2
lib/runtime-rotation-proxy.ts:935-938
**Misindented `else` block body in preferred-account path**

`accountManager.markSwitched` and `return preferred` sit at the same tab depth as `} else {` instead of being indented inside it. The code is syntactically valid, but any maintainer scanning this block will misread the control flow — the two statements look like they are unconditional siblings of the inner `if/else` rather than the `else` body. This is the only place in `chooseAccount` where the skip-reason branch got this indentation treatment; all other new branches are correctly indented.

### Issue 2 of 2
lib/runtime-rotation-proxy.ts:1195-1205
**`lastStaleRuntimeReloadAt` not set on `loadFromDisk` failure**

`lastStaleRuntimeReloadAt` is only assigned when `loadFromDisk` succeeds; the `.catch` branch returns `null` without touching it. This means successive requests that all hit the recovery path (each with `reloadedAfterNoAccount = false`) can each trigger a separate `loadFromDisk` call immediately after a failed one with no inter-request cooldown. On windows the rapid burst of file reads can produce `EBUSY`/`EPERM` errors that compound the original failure. setting `lastStaleRuntimeReloadAt = Date.now()` inside the `.catch` block, before returning null, would apply the 1 s dedup window on failure as well.

Reviews (3): Last reviewed commit: "test: cover reset runtime review paths" | Re-trigger Greptile

@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 May 11, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

adds runtime observability overlay to the proxy's account selection and forecast evaluation. AccountManager exposes skip reasons, the runtime observability snapshot tracks pool exhaustion and policy blocks, the proxy collects per-account skip reasons during selection and can reload state mid-request, and forecast evaluation consults quota cache and runtime overlays to refine availability predictions. a new rotation reset-runtime cli command clears volatile tracking state.

Changes

Runtime State Observability & Account Selection

Layer / File(s) Summary
AccountManager API
lib/accounts.ts:648–671
new getAccountRuntimeSkipReason(index, family, model?) returns specific unavailability reason (missing/disabled/workspace-disabled/rate-limited/cooling-down/circuit-open) or null. new static resetVolatileRuntimeState() calls resetTrackers() + resetAllCircuitBreakers().
Observability State Model
lib/runtime/runtime-observability.ts:50–59, 249–284
RuntimeObservabilitySnapshot gains optional fields for pool exhaustion (reason, retry-after, skip reasons), runtime reset/reload timestamps/reasons, account skip reasons, and policy-blocking (blocked indexes, reasons). new exports: recordRuntimePoolExhaustion(...), recordRuntimeReload(reason), recordRuntimeReset(reason) mutate snapshot and clear related state on reset.
Proxy Selection & Skip Reason Collection
lib/runtime-rotation-proxy.ts:868–979, 1295–1353
chooseAccount now accepts optional skipReasons?: Map<number, string> and populates it via getAccountRuntimeSkipReason(...) for pinned/preferred/fallback paths. pinned accounts hard-fail if unavailable. when selection fails under specific conditions (not pinned, no policy blocks, no relevant skip reasons), proxy resets volatile state, reloads AccountManager from disk, clears skip/attempted state, and retries. main loop tracks skip reasons in a Map<number, string>.
Pool Exhaustion Reporting
lib/runtime-rotation-proxy.ts:1019–1042, 1735–1740
writePoolExhausted now accepts accountSkipReasons?: Record<string, string>, records via recordRuntimePoolExhaustion(...), and includes in json response under error.account_skip_reasons. policy-blocking is recorded via mutateRuntimeObservabilitySnapshot on evaluation.
Forecast Overlay Input
lib/forecast.ts:22–30, 225–258
ForecastAccountInput gains optional quotaCache, allAccounts, runtimeOverlay. new RuntimeForecastOverlay interface carries accountSkipReasons, lastPoolExhaustionSkipReasons, policyBlockedIndexes. evaluateForecastAccount consults quota cache (sets delayed/waitMs/reasons if exhausted) then applies overlay: marks unavailable if policy-blocked, or marks unavailable for circuit-open/token-exhausted skip reasons, with increased riskScore.
Command Wiring & Forecast Output
lib/codex-manager.ts:2545, lib/codex-manager/commands/forecast.ts:6–27, 85–88, 115, 140–174, 220–224, 365–367, 395–396
forecast command adds --no-runtime-overlay flag (default enabled). quota cache is loaded unconditionally. runForecastCommand calls deps.loadRuntimeObservabilitySnapshot() when overlay enabled. evaluateForecastAccounts input extended with quotaCache, allAccounts, runtimeOverlay. json output includes runtimeOverlay: options.runtimeOverlay.
Report Preload & Doctor Check
lib/codex-manager/commands/report.ts:318, 457, 526, lib/codex-manager/repair-commands.ts:40, 1926–1945, 1965–1989
report preloads runtime snapshot once and reuses for overlay. doctor computes runtimeForecastResults with overlay and adds forecast-runtime-alignment check: warns when accounts diverge (ready offline but unavailable in runtime overlay), including per-account skip reasons.
Reset-Runtime Command
lib/codex-manager/commands/rotation.ts:3–43, 99–175, 739–741
new runResetRuntime subcommand calls AccountManager.resetVolatileRuntimeState(), records reset reason, optionally unbinds/rebinds codex app (via deps.unbindCodexApp/deps.bindCodexApp), outputs json or human-readable status. returns 0 on success, 1 on bind-restart failure or option errors. dispatcher routes reset-runtime subcommand.
Tests
test/codex-manager-rotation-command.test.ts:214–230, test/forecast.test.ts, test/runtime-observability.test.ts:150–183, test/runtime-rotation-proxy.test.ts:1592–1646
new rotation command test verifies json output and unbind/bind calls. forecast test covers quota-cache exhaustion (delayed) and pool-exhaustion skip-reason overrides. observability test normalizes and persists pool exhaustion with skip reasons. proxy test verifies pool-exhaustion response includes per-account skip-reason map and matching hint.

Sequence Diagram

sequenceDiagram
    participant Client as Codex CLI
    participant Proxy as Runtime Proxy
    participant AccountMgr as AccountManager
    participant Forecast as Forecast Engine
    participant Observability as Observability Snapshot
    
    Client->>Proxy: POST /responses (request)
    Proxy->>Observability: Load persisted snapshot
    Observability-->>Proxy: Runtime state (skip reasons, policy blocks)
    
    Proxy->>Proxy: Create skipReasons Map
    loop Account Selection
        Proxy->>AccountMgr: chooseAccount(..., skipReasons)
        AccountMgr->>AccountMgr: getAccountRuntimeSkipReason(index)
        alt Account available
            AccountMgr-->>Proxy: null (usable)
        else Account unavailable
            AccountMgr-->>Proxy: skip reason string
            Proxy->>skipReasons: populate skip reason
        end
    end
    
    alt All selection attempts fail
        Proxy->>Proxy: Check: not pinned, no policy blocks
        Proxy->>AccountMgr: resetVolatileRuntimeState()
        AccountMgr->>Observability: record reset
        Proxy->>AccountMgr: reload from disk
        Proxy->>Proxy: clear skipReasons, retry
    end
    
    alt Account selected
        Proxy->>Client: Route request to account
        Client-->>Proxy: Success response
    else No account available
        Proxy->>Observability: recordRuntimePoolExhaustion(skipReasons)
        Observability->>Observability: Persist pool exhaustion
        Proxy->>Client: 503 Service Unavailable + account_skip_reasons
    end
Loading
sequenceDiagram
    participant Doctor as Doctor Command
    participant ForecastModule as Forecast Module
    participant QuotaCache as Quota Cache
    participant RuntimeOverlay as Runtime Overlay
    
    Doctor->>Doctor: evaluateForecastAccounts (offline)
    ForecastModule->>ForecastModule: Check live/refresh state
    ForecastModule-->>Doctor: forecastResults
    
    Doctor->>RuntimeOverlay: loadPersistedRuntimeObservabilitySnapshot()
    RuntimeOverlay-->>Doctor: snapshot (skip reasons, policy blocks, pool exhaustion)
    
    Doctor->>Doctor: evaluateForecastAccounts (runtime overlay)
    ForecastModule->>QuotaCache: Find quota entry
    QuotaCache-->>ForecastModule: Cache data
    alt Cache exhausted
        ForecastModule->>ForecastModule: Set delayed, high risk
    end
    
    alt Policy blocks account
        ForecastModule->>ForecastModule: Mark unavailable
    else Pool exhaustion skip reason
        ForecastModule->>ForecastModule: Mark unavailable, high risk
    end
    ForecastModule-->>Doctor: runtimeForecastResults
    
    Doctor->>Doctor: Compare offline vs runtime
    alt Divergence detected
        Doctor->>Doctor: Add forecast-runtime-alignment warning
        Doctor->>Doctor: Include skip reason in details
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

bug


detailed review guidance

state mutation & concurrency: watch lib/runtime-rotation-proxy.ts:1123 where accountManager becomes let and can reload mid-request. verify that concurrent requests don't race on reload or that reload timing is safe. no lock visible, so check if the reload-gated condition (not pinned, no policy blocks, specific skip reasons) sufficiently limits when reload happens and whether multiple concurrent requests could trigger simultaneous reloads.

skip reason mapping: chooseAccount now populates skipReasons: Map<number, string> inline. the map is created per-request in the proxy loop (lib/runtime-rotation-proxy.ts:1295). verify that skipped indexes are only added when they are genuinely unavailable (not just attempted). test coverage at test/runtime-rotation-proxy.test.ts:1592 checks the final pool-exhaustion response format but should confirm intermediate selection paths also populate map correctly.

quota cache & reset timing: lib/codex-manager/commands/forecast.ts loads quota cache unconditionally, but lib/runtime-rotation-proxy.ts reload logic doesn't mention quota cache invalidation. if quota cache is stale after reload, will forecast re-evaluation see fresh data? check whether quota cache should be part of the volatile reset or if it's separately persisted.

windows edge case: lib/codex-manager/commands/rotation.ts:111–175 unbinds/rebinds the codex app but doesn't show platform-specific handling. test that unbind/bind logic works on windows (where app lifecycle may differ from macos). check if missing codex app gracefully degrades (already appears to via optional deps.bindCodexApp).

missing regression test: no test verifies that the proxy's reload-then-retry flow actually succeeds after a reset. test/codex-manager-rotation-command.test.ts tests the reset command itself, but no integration test confirms that a 503 followed by reset + reload actually routes a subsequent request successfully. consider adding a proxy integration test that simulates: (1) initial selection failure + pool exhaustion, (2) reset-runtime command, (3) subsequent request succeeds.

doctor check output: lib/codex-manager/repair-commands.ts:1965–1989 emits per-account skip reasons in forecast-runtime-alignment details, but doesn't clearly distinguish between reasons from lastPoolExhaustionSkipReasons vs currently failing accounts. the warning message could benefit from clarity on whether the reason is historical (from last pool exhaustion) or current (from this runtime snapshot).

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format (type: summary), is lowercase imperative, and clearly summarizes the main fix.
Linked Issues check ✅ Passed PR directly addresses #479: adds runtime pool exhaustion diagnostics, implements stale state recovery with reload logic, aligns forecast/report/doctor, and adds reset-runtime command.
Out of Scope Changes check ✅ Passed All changes directly support issue #479 objectives: runtime diagnostics, recovery mechanism, forecast alignment, and reset command. No unrelated refactoring detected.
Description check ✅ Passed PR description comprehensively covers summary, validation steps, risk assessment, and addresses linked issue objectives with clear recovery strategy.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #479

✨ 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 fix/issue-479-runtime-pool-diagnostics
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/issue-479-runtime-pool-diagnostics

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 and usage tips.

Comment thread lib/runtime-rotation-proxy.ts
Comment thread lib/runtime-rotation-proxy.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/runtime-rotation-proxy.ts (1)

1288-1292: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

refresh the pool limits after reloading from disk.

lib/runtime-rotation-proxy.ts:1288-1292 snapshots accountCount and transientAttemptLimit before the fallback reload. after lib/runtime-rotation-proxy.ts:1348-1352, the retry still uses those old values, so newly reloaded accounts on disk can be skipped entirely. recompute the derived counts from the new manager before continue, and cover it with a vitest case in test/runtime-rotation-proxy.test.ts where the reload increases the pool size.

suggested fix
-			const accountCount = accountManager.getAccountCount();
-			const transientAttemptLimit = Math.max(
+			let accountCount = accountManager.getAccountCount();
+			let transientAttemptLimit = Math.max(
 				1,
 				Math.min(accountCount, maxRuntimeAccountAttempts),
 			);
...
 						accountManager = await AccountManager.loadFromDisk();
+						accountCount = accountManager.getAccountCount();
+						transientAttemptLimit = Math.max(
+							1,
+							Math.min(accountCount, maxRuntimeAccountAttempts),
+						);
 						recordRuntimeReload("pool-exhausted-no-account");
 						accountSkipReasons.clear();
 						attemptedIndexes.clear();

Also applies to: 1314-1317, 1348-1352

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/runtime-rotation-proxy.ts` around lines 1288 - 1292, The loop caches
accountCount and transientAttemptLimit (computed from
accountManager.getAccountCount() and maxRuntimeAccountAttempts) before a
fallback reload, so after the reload you must re-read
accountManager.getAccountCount() and recompute transientAttemptLimit before the
continue (i.e., update the variables at the sites around the current
transientAttemptLimit/continue logic at runtime-rotation-proxy.ts lines near the
fallback reload), and add a vitest in test/runtime-rotation-proxy.test.ts that
simulates disk reload increasing accounts and verifies the retry uses the new
pool size; also apply the same recompute to the other occurrences noted (around
the blocks corresponding to the 1314–1317 and 1348–1352 regions).
lib/codex-manager/repair-commands.ts (1)

1926-1988: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

add doctor-command vitest regressions for runtime alignment checks

lib/codex-manager/repair-commands.ts:1926-1988 introduces new runtime/disk divergence diagnostics. please add tests that assert forecast-runtime-alignment warning generation and null/corrupt snapshot fallback behavior so this path stays stable.

As per coding guidelines, "lib/**: ... verify every change cites affected tests (vitest) ...".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/codex-manager/repair-commands.ts` around lines 1926 - 1988, Add vitest
unit tests for the new runtime/disk divergence logic: call
evaluateForecastAccounts and recommendForecastAccount via the repair flow (the
code that uses loadPersistedRuntimeObservabilitySnapshot, runtimeOverlay,
runtimeForecastResults and adds checks via addCheck) to assert that when
runtimeForecastResults mark an account as unavailable while forecastResults mark
it as ready the generated check with key "forecast-runtime-alignment" has
severity "warn", its message contains the correct count (e.g., "1 account(s)
look ready on disk but unavailable in runtime state"), and the details string
includes either runtime reasons joined by "; " or the fallback "runtime
unavailable". Also add tests that simulate
loadPersistedRuntimeObservabilitySnapshot throwing/corrupt returning null so
runtimeOverlay is null/undefined and verify the code falls back cleanly (no
crash) and produces the expected alignment result (ok when no divergence, warn
when divergence exists). Use the same public symbols to locate behavior:
loadPersistedRuntimeObservabilitySnapshot, evaluateForecastAccounts,
recommendForecastAccount and the check key "forecast-runtime-alignment".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/codex-manager/commands/forecast.ts`:
- Around line 171-174: Add a new CLI-level vitest that runs the forecast command
twice (once normal, once with the --no-runtime-overlay flag) using a subprocess
helper (execa/spawn) to capture stdout JSON; assert that options.runtimeOverlay
is honored by verifying the forecast "reasons" and "availability" differ between
runs (overlay present vs suppressed) and that the produced JSON contains a field
reflecting overlay state (e.g., runtimeOverlay:false when --no-runtime-overlay
is passed). Locate the flag parsing code in forecast.ts (the branch that sets
options.runtimeOverlay = false) and the JSON output logic around the forecast
result emission (near the code at or around line 395) to pick the exact output
keys to assert; make the test deterministic by stubbing/mocking any randomness
or external calls the command invokes.

In `@lib/codex-manager/commands/report.ts`:
- Line 457: Add a deterministic vitest that exercises the report command
end-to-end and asserts both (1) that when a runtime snapshot with an overlay is
successfully loaded the emitted runtime payload contains runtimeOverlay and the
expected availability reasons applied to forecast inputs, and (2) that when
snapshot loading fails the command falls back to the snapshot-load failure flow
and still emits a valid runtime payload with the fallback marker. In the test
locate the report command entry point (the exported handler in the report
command module) and stub/mocks for the snapshot loader and emitter: mock the
snapshot loader to first return a payload containing
runtimeSnapshot/runtimeOverlay and availability reasons, then mock it to throw
to test the fallback; assert the emitted payloads include the modified forecast
inputs, the runtimeOverlay field, and the fallback indicator where appropriate;
make the test deterministic by seeding or freezing time and controlling any
randomness used by the report flow.
- Line 318: Wrap the await of deps.loadRuntimeObservabilitySnapshot?.() in a
try/catch so any thrown read/parse error is treated as non-fatal: if the call
succeeds assign its value to runtimeSnapshot, but on any exception log a
diagnostic (or use existing logger) and set runtimeSnapshot = null so report can
continue; reference the runtimeSnapshot variable and the
deps.loadRuntimeObservabilitySnapshot call in the report command to locate where
to add the try/catch and logging.

In `@lib/forecast.ts`:
- Around line 242-257: The overlay handling currently only marks availability
"unavailable" for overlayReason values "circuit-open" and "token-exhausted",
leaving other runtime skip reasons as "ready"; update the logic in
lib/forecast.ts (the block using overlay, overlayReason, availability,
riskScore, and reasons) so that any overlayReason !== null and !==
"already-attempted" clears the "ready" state (set availability to
"unavailable"), increment riskScore appropriately (pick a sensible default e.g.
+50 for generic skips), and push the runtime skip reason into reasons; then add
Vitest regressions in test/forecast.test.ts asserting that accounts with
overlayReason values like "cooling-down:123" and "rate-limited" no longer report
availability "ready" and include the runtime skip reason in reasons.
- Around line 225-239: The code incorrectly uses Math.min on resetAts so when
both quota windows are exhausted we pick the earlier reset instead of the later
one; update the logic in the block that uses findQuotaCacheEntryForAccount /
isQuotaCacheEntryExhausted (the quotaEntry handling that builds resetAts and
computes quotaWait) to use Math.max(...resetAts) - now so waitMs reflects the
last reset, and keep the existing adjustments to availability, riskScore,
reasons, and appendWaitReason; then add a vitest regression in
test/forecast.test.ts that constructs a quotaEntry with both primary and
secondary exhausted and different resetAtMs values and asserts the computed
waitMs (and resulting availability/reasons) uses the later reset timestamp.

In `@lib/runtime-rotation-proxy.ts`:
- Around line 1123-1124: The code currently mutates the process-wide
accountManager (variable accountManager and reassignments after
AccountManager.loadFromDisk) during an awaited reload, which allows overlapping
requests to use stale ManagedAccount instances and lose persisted cooldown/save
state; fix by pinning a manager instance per request (capture const
requestAccountManager = options.accountManager ?? (await
AccountManager.loadFromDisk()) at request start) and change all places that call
ManagedAccount methods and cooldown/save to use the pinned requestAccountManager
rather than the module-scoped accountManager, serialize global swaps/reloads
behind a mutex/queue when you must replace the process-wide reference, and on
shutdown ensure you flush all known manager instances (not just the last one);
add a vitest regression in test/runtime-rotation-proxy.test.ts that concurrently
triggers a reload (AccountManager.loadFromDisk) and another request that
persists cooldown state to assert no state is lost, and ensure the new request
queue logic correctly retries or serializes on EBUSY/429 per the lib/**
guidelines.

---

Outside diff comments:
In `@lib/codex-manager/repair-commands.ts`:
- Around line 1926-1988: Add vitest unit tests for the new runtime/disk
divergence logic: call evaluateForecastAccounts and recommendForecastAccount via
the repair flow (the code that uses loadPersistedRuntimeObservabilitySnapshot,
runtimeOverlay, runtimeForecastResults and adds checks via addCheck) to assert
that when runtimeForecastResults mark an account as unavailable while
forecastResults mark it as ready the generated check with key
"forecast-runtime-alignment" has severity "warn", its message contains the
correct count (e.g., "1 account(s) look ready on disk but unavailable in runtime
state"), and the details string includes either runtime reasons joined by "; "
or the fallback "runtime unavailable". Also add tests that simulate
loadPersistedRuntimeObservabilitySnapshot throwing/corrupt returning null so
runtimeOverlay is null/undefined and verify the code falls back cleanly (no
crash) and produces the expected alignment result (ok when no divergence, warn
when divergence exists). Use the same public symbols to locate behavior:
loadPersistedRuntimeObservabilitySnapshot, evaluateForecastAccounts,
recommendForecastAccount and the check key "forecast-runtime-alignment".

In `@lib/runtime-rotation-proxy.ts`:
- Around line 1288-1292: The loop caches accountCount and transientAttemptLimit
(computed from accountManager.getAccountCount() and maxRuntimeAccountAttempts)
before a fallback reload, so after the reload you must re-read
accountManager.getAccountCount() and recompute transientAttemptLimit before the
continue (i.e., update the variables at the sites around the current
transientAttemptLimit/continue logic at runtime-rotation-proxy.ts lines near the
fallback reload), and add a vitest in test/runtime-rotation-proxy.test.ts that
simulates disk reload increasing accounts and verifies the retry uses the new
pool size; also apply the same recompute to the other occurrences noted (around
the blocks corresponding to the 1314–1317 and 1348–1352 regions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a45c5abe-657c-497f-85f8-35a53d5765d1

📥 Commits

Reviewing files that changed from the base of the PR and between 99b8efc and ef75293.

📒 Files selected for processing (13)
  • lib/accounts.ts
  • lib/codex-manager.ts
  • lib/codex-manager/commands/forecast.ts
  • lib/codex-manager/commands/report.ts
  • lib/codex-manager/commands/rotation.ts
  • lib/codex-manager/repair-commands.ts
  • lib/forecast.ts
  • lib/runtime-rotation-proxy.ts
  • lib/runtime/runtime-observability.ts
  • test/codex-manager-rotation-command.test.ts
  • test/forecast.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/codex-manager/repair-commands.ts
  • lib/codex-manager.ts
  • lib/forecast.ts
  • lib/codex-manager/commands/rotation.ts
  • lib/accounts.ts
  • lib/codex-manager/commands/report.ts
  • lib/codex-manager/commands/forecast.ts
  • lib/runtime-rotation-proxy.ts
  • lib/runtime/runtime-observability.ts
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/codex-manager-rotation-command.test.ts
  • test/runtime-rotation-proxy.test.ts
  • test/runtime-observability.test.ts
  • test/forecast.test.ts
🔇 Additional comments (2)
lib/codex-manager.ts (1)

2545-2545: clean forecast wiring for runtime overlay loader

the dependency injection at lib/codex-manager.ts:2545 is clean and keeps command composition explicit.

test/forecast.test.ts (1)

81-144: good deterministic regression coverage for new forecast inputs

these additions at test/forecast.test.ts:81-144 directly lock in quota-cache exhaustion and runtime-overlay skip behavior with stable timestamps and explicit assertions.

Comment thread lib/codex-manager/commands/forecast.ts
Comment thread lib/codex-manager/commands/report.ts Outdated
Comment thread lib/codex-manager/commands/report.ts
Comment thread lib/forecast.ts
Comment thread lib/forecast.ts
Comment thread lib/runtime-rotation-proxy.ts Outdated
@ndycode
ndycode merged commit 6e13a4c into main May 11, 2026
2 checks passed
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.

[bug] Accounts look healthy but codex throws 503

1 participant