Skip to content

refactor: extract shared account view helpers (Architecture PR #1)#33

Closed
ndycode wants to merge 13 commits intodevfrom
refactor/arch-pr1-shared-account-helpers
Closed

refactor: extract shared account view helpers (Architecture PR #1)#33
ndycode wants to merge 13 commits intodevfrom
refactor/arch-pr1-shared-account-helpers

Conversation

@ndycode
Copy link
Owner

@ndycode ndycode commented Mar 3, 2026

Summary - extract duplicated account-view formatting logic into a shared helper module - reuse the shared helpers in index.ts and lib/codex-manager.ts - add focused unit + regression tests to lock active-index fallback and rate-limit formatting behavior ## Why PR #1 from the architecture roadmap reduces coupling and duplicate workflow logic without changing external behavior. ## Validation - npm run build - npm run lint - npm run typecheck - npm test ## PR Resolution Update (2026-03-05) ### What Changed - Normalized account index handling to finite integers across shared helpers and event intake ( esolveActiveIndex, setActiveIndexForAllFamilies, emoveAccountAndReconcileActiveIndexes, ^Gccount.select event path). - Hardened rate-limit rendering by ignoring non-finite reset timestamps before min-selection. - Updated manager delete flow to reconcile active selections instead of forcing every family to index 0, and aligned reset path to the manager wrapper createEmptyAccountStorage() constructor. - Strengthened clone isolation in storage views by cloning nested ateLimitResetTimes objects. - Added/updated regression tests for fractional/non-finite index and rate-limit inputs, non-integer remove indices, nested clone isolation, manager delete reindexing, and event fractional index coercion. ### How To Test - pm run typecheckn- pm run lintn- pm run buildn- pm testn ### Risk / Rollout Notes - Behavior change is intentional for delete reconciliation: removing a non-active account now preserves the previously selected account by reindexing instead of resetting to account 1. - Status output now includes explicit reset detail text for rate-limited accounts ( esets in ...), improving operator clarity. ## PR Resolution Update (2026-03-05, follow-up) ### What changed - Replaced clamp-only delete normalization in index.ts with per-removal reconciliation using removeAccountAndReconcileActiveIndexes, including the flagged-removal path and manage-delete path. - Guarded manage-delete persistence in lib/codex-manager.ts so save/log success only occurs when removal actually succeeds. - Updated test/index.test.ts mock MODEL_FAMILIES to match production (gpt-5-codex, codex-max, codex, gpt-5.2, gpt-5.1) and added a multi-family delete reindex regression. - Added fractional manage-delete regression in test/codex-manager-cli.test.ts. ### How to test - npm test -- test/index.test.ts test/codex-manager-cli.test.ts - npm run typecheck - npm run lint - npm run build - npm test ### Risk / rollout notes - No intended external behavior change. - Primary risk area was cross-family active-index drift after deletions; now covered by helper-based reconciliation and regression tests.

PR Resolution Update (2026-03-05, follow-up 2)

What changed

  • Updated index.ts codex-remove flow to normalize family active-index state after removeAccountAndReconcileActiveIndexes(...).
  • This keeps delete-flow behavior consistent with other removal paths and prevents persisting undefined activeIndexByFamily on older storage snapshots.
  • Added regression in test/index.test.ts for remove-with-undefined-family-map normalization.

How to test

  • npm test -- test/index.test.ts
  • npm run typecheck
  • npm run lint

Risk / rollout notes

  • No external behavior change beyond deletion-state normalization consistency.

Follow-up Review Resolution (2026-03-05)

  • Updated removeAccountAndReconcileActiveIndexes to always initialize and reconcile activeIndexByFamily, including legacy storage shapes where the field is missing.
  • Added regression in test/active-index.test.ts for deletion from legacy storage without a family map.

How to test

  • npm test -- test/active-index.test.ts
  • npm run typecheck
  • npm run lint

Risk / rollout notes

  • Intentional normalization hardening: delete reconciliation now consistently preserves and normalizes family-index state.

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

this pr extracts duplicated active-index and rate-limit formatting logic from index.ts, lib/codex-manager.ts, and lib/codex-cli/sync.ts into three focused shared modules (lib/accounts/account-view.ts, lib/accounts/active-index.ts, lib/accounts/storage-view.ts), fixing delete-reindex semantics to preserve relative selection position rather than resetting to account 1 and hardening index/timestamp inputs against fractional and non-finite values throughout.

key changes and observations:

  • removeAccountAndReconcileActiveIndexes now always initializes activeIndexByFamily for legacy storage where the field was undefined, closing the pre-migration persistence gap across all delete paths
  • cloneAccountStorage preserves undefined activeIndexByFamily rather than coercing to {}, which is the correct behavior but means first-reconcile of pre-migration storage will still detect a changed = true and trigger a save on windows — no ebusy retry guard exists at either saveAccounts call site (codex-remove tool, account.select event handler)
  • formatActiveIndexByFamilyLabels guards with Number.isFinite but not Number.isInteger, so fractional values from unrecovered legacy snapshots would render as "1.5"-style labels rather than a clean "-"
  • handleManageAction in codex-manager.ts silently swallows out-of-bounds delete indices (the inner fractional guard logs an error, but the outer bounds check exits without any log when the index is stale/out-of-range)
  • regression coverage is thorough: fractional and non-finite index coercion, per-family reindex semantics, legacy storage without family map, and multi-family delete reindex are all exercised; no vitest coverage for the saveAccounts ebusy path on windows

Confidence Score: 4/5

  • safe to merge — no external behavior change, refactor is well-tested, and the only remaining gaps are a minor display edge case and a pre-existing windows save-retry gap
  • core logic is correct and well-covered by regression tests. the fractional label display issue is cosmetic and only affects un-normalized legacy storage. the ebusy save risk is pre-existing and not worsened by this pr. the silent out-of-bounds drop in handleManageAction is minor since it can only be reached via stale TUI state.
  • lib/accounts/account-view.ts (fractional label guard), lib/codex-manager.ts (silent delete out-of-bounds path), index.ts and lib/codex-manager.ts saveAccounts call sites (no windows ebusy retry)

Important Files Changed

Filename Overview
lib/accounts/account-view.ts new shared module extracting resolveActiveIndex, formatRateLimitEntry, formatRateLimitStatusByFamily, and formatActiveIndexByFamilyLabels; adds Number.isFinite guard on rate-limit timestamps; minor: formatActiveIndexByFamilyLabels should also check Number.isInteger to avoid fractional display labels from legacy storage
lib/accounts/active-index.ts new module with setActiveIndexForAllFamilies, normalizeActiveIndexByFamily, and removeAccountAndReconcileActiveIndexes; correctly guards non-integer targetIndex, initializes activeIndexByFamily for legacy storage (undefined → {}), and uses Math.trunc for consistent integer normalization throughout
lib/accounts/storage-view.ts new module with createEmptyAccountStorage and cloneAccountStorage; cloneAccountStorage now preserves undefined activeIndexByFamily (unlike old cloneStorage which coerced to {}), correctly deep-clones rateLimitResetTimes per account
lib/codex-cli/sync.ts removes local createEmptyStorage and cloneStorage, delegates to shared helpers; writeFamilyIndexes and normalizeStoredFamilyIndexes are now thin wrappers; change-detection comparison via JSON.stringify is order-dependent but stable since helpers write keys in fixed MODEL_FAMILIES order
lib/codex-manager.ts removes 51 lines of duplicate local helpers, delegates to shared modules; handleManageAction delete path correctly guards fractional index via removeAccountAndReconcileActiveIndexes; out-of-bounds delete index exits silently without logging; no EBUSY retry around saveAccounts
index.ts removes inline resolveActiveIndex/getRateLimitResetTimeForFamily/formatRateLimitEntry; codex-remove now calls removeAccountAndReconcileActiveIndexes + normalizeActiveIndexByFamily; account.select event floors fractional index before setActiveIndexForAllFamilies; saveAccounts lacks EBUSY retry on Windows

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[delete / account.select / rotate event] --> B{code path}

    B --> C[codex-remove tool\nindex.ts]
    B --> D[account.select event\nindex.ts]
    B --> E[handleManageAction delete\ncodex-manager.ts]
    B --> F[batch-delete wrapper\nindex.ts]

    C --> G[removeAccountAndReconcileActiveIndexes\nlib/accounts/active-index.ts]
    E --> G
    F --> G

    G --> H{targetIndex valid?\nNumber.isInteger + bounds}
    H -- no --> I[return false\nno mutation]
    H -- yes --> J[splice account\nreconcile activeIndex\ninitialize activeIndexByFamily\nreconcile per-family indexes]

    C --> K[normalizeActiveIndexByFamily\nclearFamilyMapWhenEmpty: true]
    F --> K

    D --> L[Math.floor rawIndex\nsetActiveIndexForAllFamilies\nlib/accounts/active-index.ts]

    J --> M[saveAccounts\nno EBUSY retry ⚠️]
    K --> M
    L --> M

    style I fill:#f9f,stroke:#333
    style M fill:#ff9,stroke:#333
Loading

Fix All in Codex

Last reviewed commit: d818232

Context used:

  • Rule from dashboard - What: Every code change must explain how it defends against Windows filesystem concurrency bugs and ... (source)

Introduce a shared account-view helper module and reuse it across the plugin entrypoint and CLI manager to remove duplicated formatting logic while preserving behavior.

Add unit/regression tests to lock formatting and active-index fallback behavior.

Co-authored-by: Codex <noreply@openai.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

walkthrough

moved active-index, rate-limit, and storage helpers into new public modules (lib/accounts/account-view.ts, lib/accounts/active-index.ts, lib/accounts/storage-view.ts), replaced inline implementations across cli/manager with imports, and added unit tests for the new helpers and cli outputs.

changes

Cohort / File(s) Summary
account view utilities
lib/accounts/account-view.ts
adds public views and helpers: resolveActiveIndex, getRateLimitResetTimeForFamily, formatRateLimitEntry, formatRateLimitStatusByFamily, and formatActiveIndexByFamilyLabels for per-family rate-limit and index formatting.
active-index helpers
lib/accounts/active-index.ts
introduces createActiveIndexByFamily, setActiveIndexForAllFamilies, normalizeActiveIndexByFamily, removeAccountAndReconcileActiveIndexes; centralizes per-family index creation, propagation, clamping, and reconciliation.
storage helpers
lib/accounts/storage-view.ts
adds createEmptyAccountStorage and cloneAccountStorage to standardize storage initialization and cloning (including rateLimitResetTimes cloning).
codex manager / cli integrations
lib/codex-manager.ts, lib/codex-cli/sync.ts, index.ts
replaced local active-index and rate-limit logic with imports from the new account modules; call sites now use setActiveIndexForAllFamilies, normalizeActiveIndexByFamily, resolveActiveIndex, and formatRateLimitEntry.
tests added/updated
test/account-view.test.ts, test/active-index.test.ts, test/account-storage-view.test.ts, test/codex-manager-cli.test.ts, test/index.test.ts
adds comprehensive tests for active-index creation/normalization/removal, storage clone/init, rate-limit formatting, and cli behavior including per-family indices and "resets in" output.

sequence diagram(s)

(silently skipped)

estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

observations from a senior staff engineer review

  • missing regression tests for concurrent mutations. add tests that simulate interleaved reads/writes to storage.activeIndex and storage.activeIndexByFamily to validate normalizeActiveIndexByFamily and setActiveIndexForAllFamilies under race-like conditions. see lib/accounts/active-index.ts:1 and add tests in test/active-index.test.ts:1.
  • test gaps for family-prefix matching. confirm getRateLimitResetTimeForFamily covers both exact-match and prefix-match branches. current tests mention prefixes but must assert behavior for codex vs codex:subfamily. check lib/accounts/account-view.ts:1 and test/account-view.test.ts:1.
  • missing per-family override case in tests. add a unit that sets storage.activeIndexByFamily with a differing family value and verifies resolveActiveIndex returns that per-family value. reference lib/accounts/account-view.ts:1 and test/account-view.test.ts:1.
  • windows / path-case edge case. verify runtime import paths and filename casing for lib/accounts/account-view.ts:1 imports (rate-limits.js, prompts/codex.js) to avoid CI/windows failures. run CI on windows runners.
  • ensure all call sites updated. grep for remaining uses of resolveActiveIndex, getRateLimitResetTimeForFamily, and formatRateLimitEntry to avoid orphaned references. check index.ts:1, lib/codex-manager.ts:1, and run a repo-wide search.
  • preserve unrelated keys in activeIndexByFamily. tests should assert that setActiveIndexForAllFamilies does not remove unknown keys. confirm lib/accounts/active-index.ts:1 behavior matches those assertions and add explicit test in test/active-index.test.ts:1.
  • document concurrency assumption. add brief comments in lib/accounts/active-index.ts:1 and lib/accounts/account-view.ts:1 stating that storage mutations should be externally synchronized or single-threaded during reads.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format with type 'refactor', descriptive summary under 72 characters, and clearly describes the main change of extracting shared account helpers.
Description check ✅ Passed PR description is comprehensive and well-structured, covering summary, changes, validation steps, and multiple resolution updates with detailed rollout notes.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/arch-pr1-shared-account-helpers

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/accounts/account-view.ts`:
- Around line 35-41: Update the loop that scans Object.entries(times) to skip
non-finite numbers by using Number.isFinite(value) (so NaN/Infinity/-Infinity
are ignored) before comparing to now and updating minReset; this change affects
the code path that later calls formatRateLimitEntry, so ensure
formatRateLimitEntry still receives only finite timestamps. Add regression tests
in test/account-view.test.ts that set rateLimitResetTimes to NaN, Infinity, and
-Infinity and assert the rendered output is well-formed (i.e., those entries are
ignored and do not produce malformed output), referencing the same account-view
logic that computes minReset and formats entries.
- Around line 20-23: The active-index clamp currently can return fractional
values; coerce the chosen rawCandidate to an integer before clamping (use
Math.trunc or equivalent) so storage.activeIndexByFamily and storage.activeIndex
never produce fractional indices; update the normalization in
lib/accounts/account-view.ts where storage.activeIndexByFamily?.[family] ??
storage.activeIndex is processed to first coerce to an integer then apply
Math.max(0, Math.min(..., total - 1)). Add a regression vitest in
test/account-view.test.ts that sets fractional values for activeIndex and
activeIndexByFamily and asserts the returned active index is an integer and
within bounds; ensure the change does not log any sensitive tokens/emails and
mention the new test in the test suite metadata.

In `@test/account-view.test.ts`:
- Around line 9-33: Add a test checking resolveActiveIndex when accounts is an
empty array so the special-case total === 0 behavior is locked in: call
resolveActiveIndex({ activeIndex: X, activeIndexByFamily: {...}?, accounts: []
}) and assert it returns 0; place it alongside the existing tests that exercise
clamping and normalization to verify the function returns 0 for empty accounts
(refer to resolveActiveIndex in account-view tests).

In `@test/codex-manager-cli.test.ts`:
- Line 525: The test currently only asserts the log contains "rate-limited";
update the assertion to specifically verify the formatted reset indicator
produced by formatRateLimitEntry is present (e.g. assert the logged message
includes "resets in" or a regex matching "resets in .*s|m|h"), by changing the
expectation on logSpy.mock.calls (the same array used now) so it checks call[0]
for the reset phrasing; this ensures the lib/accounts/account-view.ts helper
formatRateLimitEntry is exercised instead of a generic substring match.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between d36b04f and 2013dbe.

📒 Files selected for processing (6)
  • index.ts
  • lib/accounts/account-view.ts
  • lib/codex-manager.ts
  • test/account-view.test.ts
  • test/codex-manager-cli.test.ts
  • test/index.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)
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/index.test.ts
  • test/account-view.test.ts
  • test/codex-manager-cli.test.ts
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/accounts/account-view.ts
  • lib/codex-manager.ts
🧬 Code graph analysis (3)
test/account-view.test.ts (1)
lib/accounts/account-view.ts (3)
  • resolveActiveIndex (14-23)
  • getRateLimitResetTimeForFamily (25-45)
  • formatRateLimitEntry (47-57)
lib/accounts/account-view.ts (1)
lib/prompts/codex.ts (1)
  • ModelFamily (47-52)
test/codex-manager-cli.test.ts (3)
scripts/benchmark-runtime-path.mjs (1)
  • now (84-84)
scripts/codex.js (3)
  • exitCode (505-505)
  • exitCode (532-532)
  • runCodexMultiAuthCli (501-501)
lib/codex-manager.ts (1)
  • runCodexMultiAuthCli (4001-4062)
🔇 Additional comments (5)
index.ts (1)

120-124: shared helper import wiring looks good.

the extraction hook-up is clean, and it keeps behavior centralized in lib/accounts/account-view.ts:14-57. no new windows fs or concurrency risk is introduced by this import-only change.

lib/codex-manager.ts (1)

25-28: import refactor is consistent and safe.

this import-only change cleanly reuses shared helpers from lib/accounts/account-view.ts:14-57. no new windows fs edge case or concurrency path is introduced here. affected behavior is expected to be covered by test/account-view.test.ts and test/codex-manager-cli.test.ts.

As per coding guidelines, "lib/**: 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."

test/index.test.ts (1)

734-747: lgtm - deterministic timing for rate-limit formatting test.

good refactor to capture now upfront at test/index.test.ts:734 and derive time-based fields from it. the assertion at line 747 properly locks the "resets in" formatting behavior from lib/accounts/account-view.ts:46-56.

test/account-view.test.ts (2)

35-68: good coverage of family prefix matching and edge cases.

the test at test/account-view.test.ts:43 correctly verifies that "gpt-5.1" key is excluded when querying "codex" family, exercising the prefix logic at lib/accounts/account-view.ts:36-37.


70-92: lgtm - locks the human-readable output format.

deterministic test with hardcoded timestamps. the assertion "resets in 1m 30s" at line 81 ensures formatWaitTime integration from lib/accounts/account-view.ts:55 produces expected output.

Add shared helpers for per-family active-index map creation and bulk updates, then reuse them across plugin, CLI manager, and codex-cli sync flows to reduce duplicated state-mutation logic.

Extend tests to lock switch and selection behavior for all model-family indexes.

Co-authored-by: Codex <noreply@openai.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
lib/codex-manager.ts (1)

3628-3633: ⚠️ Potential issue | 🟠 Major

deleting any account currently forces active selection to account 1.

at Line 3632 (lib/codex-manager.ts:3632), the delete path resets all family indexes to 0 unconditionally. deleting a non-active account should preserve the active selection (or shift it by one when deleting a preceding row). this creates an unexpected account switch.

i also do not see a targeted vitest regression for this manage-delete path in the changed tests (test/index.test.ts:699-753,1914-1918 focus switch/status/event flows).

proposed fix
 	if (typeof menuResult.deleteAccountIndex === "number") {
 		const idx = menuResult.deleteAccountIndex;
 		if (idx >= 0 && idx < storage.accounts.length) {
+			const previousActive = resolveActiveIndex(storage, "codex");
 			storage.accounts.splice(idx, 1);
-			setActiveIndexForAllFamilies(storage, 0);
+			const nextActive =
+				storage.accounts.length === 0
+					? 0
+					: idx < previousActive
+						? previousActive - 1
+						: Math.min(previousActive, storage.accounts.length - 1);
+			setActiveIndexForAllFamilies(storage, nextActive);
 			await saveAccounts(storage);
 			console.log(`Deleted account ${idx + 1}.`);
 		}
 		return;
 	}

as per coding guidelines, lib/**: "verify every change cites affected tests (vitest)" and test/**: "demand regression cases ... and reject changes that ... skip assertions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/codex-manager.ts` around lines 3628 - 3633, The delete path currently
calls setActiveIndexForAllFamilies(storage, 0) unconditionally which forces
every family's active account to index 0; change the logic around
menuResult.deleteAccountIndex so you compute and apply a new active index per
family instead: for each family use its current activeIndex (or 0 if missing)
and if the deleted index < currentActiveIndex decrement it by 1, if deleted
index === currentActiveIndex pick a sensible fallback (e.g., max(0,
currentActiveIndex - 1) or the next valid index) otherwise leave it unchanged,
then call a helper (or update setActiveIndexForAllFamilies to accept a
per-family mapping) and await saveAccounts(storage); also add a vitest
regression that deletes a non-active account and asserts active selection is
preserved/shifted correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@lib/codex-manager.ts`:
- Around line 3628-3633: The delete path currently calls
setActiveIndexForAllFamilies(storage, 0) unconditionally which forces every
family's active account to index 0; change the logic around
menuResult.deleteAccountIndex so you compute and apply a new active index per
family instead: for each family use its current activeIndex (or 0 if missing)
and if the deleted index < currentActiveIndex decrement it by 1, if deleted
index === currentActiveIndex pick a sensible fallback (e.g., max(0,
currentActiveIndex - 1) or the next valid index) otherwise leave it unchanged,
then call a helper (or update setActiveIndexForAllFamilies to accept a
per-family mapping) and await saveAccounts(storage); also add a vitest
regression that deletes a non-active account and asserts active selection is
preserved/shifted correctly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 2013dbe and 00dba6e.

📒 Files selected for processing (6)
  • index.ts
  • lib/accounts/active-index.ts
  • lib/codex-cli/sync.ts
  • lib/codex-manager.ts
  • test/active-index.test.ts
  • test/index.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/accounts/active-index.ts
  • lib/codex-cli/sync.ts
  • lib/codex-manager.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/active-index.test.ts
  • test/index.test.ts
🧬 Code graph analysis (5)
lib/accounts/active-index.ts (1)
lib/prompts/codex.ts (2)
  • ModelFamily (47-52)
  • MODEL_FAMILIES (58-64)
lib/codex-cli/sync.ts (1)
lib/accounts/active-index.ts (1)
  • setActiveIndexForAllFamilies (19-29)
lib/codex-manager.ts (1)
lib/accounts/active-index.ts (2)
  • createActiveIndexByFamily (8-17)
  • setActiveIndexForAllFamilies (19-29)
index.ts (1)
lib/accounts/active-index.ts (1)
  • setActiveIndexForAllFamilies (19-29)
test/index.test.ts (2)
lib/prompts/codex.ts (1)
  • MODEL_FAMILIES (58-64)
scripts/benchmark-runtime-path.mjs (1)
  • now (84-84)
🔇 Additional comments (9)
lib/accounts/active-index.ts (1)

8-29: helper extraction is clean and behavior-preserving.

the new shared helpers in lib/accounts/active-index.ts:8-29 are straightforward and keep mutation scope tight. coverage exists in test/active-index.test.ts:9-53, and this change does not add windows io, queue behavior, or sensitive logging paths.

as per coding guidelines, lib/**: "verify every change cites affected tests (vitest) ... check for logging that leaks tokens or emails."

lib/codex-cli/sync.ts (1)

7-7: delegation to shared active-index helper looks correct.

lib/codex-cli/sync.ts:162-167 now routes through setActiveIndexForAllFamilies, which keeps this path aligned with the centralized behavior from lib/accounts/active-index.ts:19-29. no new windows fs edge case or concurrency surface is introduced by this change.

Also applies to: 162-167

test/active-index.test.ts (1)

8-54: test coverage here is strong and deterministic.

test/active-index.test.ts:8-54 locks the core regressions for family map creation/update and object reuse behavior with clear assertions.

as per coding guidelines, test/**: "tests must stay deterministic and use vitest ... reject changes that ... skip assertions."

lib/codex-manager.ts (1)

25-32: shared helper adoption is consistent in create/switch paths.

the migrations at lib/codex-manager.ts:1338, lib/codex-manager.ts:2349-2355, and lib/codex-manager.ts:3853 correctly consolidate per-family index writes and keep the logic aligned with lib/accounts/active-index.ts.

Also applies to: 1338-1338, 2349-2355, 3853-3853

test/index.test.ts (1)

699-703: nice regression strengthening for family-wide active index behavior.

the added assertions in test/index.test.ts:699-703 and test/index.test.ts:1914-1918 correctly verify that all model families move together on switch/select flows. the status-case setup at test/index.test.ts:739-753 also improves timing stability for rate-limit output checks.

as per coding guidelines, test/**: "tests must stay deterministic and use vitest. demand regression cases ...".

Also applies to: 739-753, 1914-1918

index.ts (4)

120-125: imports look correct and all are used.

verified that resolveActiveIndex, getRateLimitResetTimeForFamily, and formatRateLimitEntry from lib/accounts/account-view.js are used throughout the file (lines 3030, 3388, 3403, 3447, 3572, 3586, 3610). setActiveIndexForAllFamilies from lib/accounts/active-index.js is used at lines 959 and 3522.


959-959: correct usage of setActiveIndexForAllFamilies in event handler.

storage null check at index.ts:949 guards this call. the helper sets both storage.activeIndex and populates storage.activeIndexByFamily for all model families, matching the previous inline behavior.


3522-3522: correct usage of setActiveIndexForAllFamilies in codex-switch tool.

storage null check at index.ts:3487 and targetIndex bounds validation at index.ts:3499-3513 guard this call. matches the extraction pattern described in pr objectives.


120-125: test coverage is adequate.

test/active-index.test.ts:11-37 covers setActiveIndexForAllFamilies with the relevant scenarios: initializing activeIndexByFamily when missing, setting across all families, and preserving unrelated properties. the edge cases you mentioned (empty accounts array, index at bounds) don't apply here—this function doesn't take an accounts parameter or perform bounds checking. test/account-view.test.ts:9-92 properly covers the three imported helpers (resolveActiveIndex, getRateLimitResetTimeForFamily, formatRateLimitEntry) with boundary and fallback cases.

ndycode and others added 2 commits March 4, 2026 06:26
Extract active-index normalization into a shared helper and reuse it in plugin account-flow clamping, codex-cli sync, and doctor normalization paths.

Add focused tests covering bounded normalization and empty-account map handling to preserve behavior across both clear and fill modes.

Co-authored-by: Codex <noreply@openai.com>
Extract model-family rate-limit status rendering into account-view helpers and replace duplicate status mapping logic in the plugin status output paths.

Add unit coverage for mixed limited/ok family labels to protect output behavior.

Co-authored-by: Codex <noreply@openai.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/accounts/active-index.ts`:
- Around line 13-16: The clampIndex helper and the state-write paths that call
it are currently allowing fractional and non-finite numbers to persist; coerce
incoming index values to finite integers at the shared helper boundary by
validating with Number.isFinite and using Math.trunc(Number(index)) (or 0 for
non-finite values) before clamping. Update clampIndex to accept any numeric
input, normalize it to a finite integer first, then apply the existing clamp
logic, and likewise normalize inputs in the other state-write helpers referenced
(the functions that call clampIndex around the active-index logic) so only
integer indexes are ever saved.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 00dba6e and 0d96ab3.

📒 Files selected for processing (6)
  • index.ts
  • lib/accounts/active-index.ts
  • lib/codex-cli/sync.ts
  • lib/codex-manager.ts
  • test/active-index.test.ts
  • test/index.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)
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/active-index.test.ts
  • test/index.test.ts
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-cli/sync.ts
  • lib/accounts/active-index.ts
  • lib/codex-manager.ts
🧬 Code graph analysis (5)
lib/codex-cli/sync.ts (1)
lib/accounts/active-index.ts (2)
  • setActiveIndexForAllFamilies (29-39)
  • normalizeActiveIndexByFamily (41-82)
test/index.test.ts (1)
lib/prompts/codex.ts (1)
  • MODEL_FAMILIES (58-64)
index.ts (1)
lib/accounts/active-index.ts (2)
  • setActiveIndexForAllFamilies (29-39)
  • normalizeActiveIndexByFamily (41-82)
lib/accounts/active-index.ts (1)
lib/prompts/codex.ts (2)
  • ModelFamily (47-52)
  • MODEL_FAMILIES (58-64)
lib/codex-manager.ts (1)
lib/accounts/active-index.ts (3)
  • createActiveIndexByFamily (18-27)
  • normalizeActiveIndexByFamily (41-82)
  • setActiveIndexForAllFamilies (29-39)
🔇 Additional comments (12)
index.ts (1)

120-128: good reuse of shared account-index helpers.

this is a clean extraction and the call sites are now consistent. coverage is in place for switch/event propagation in test/index.test.ts:693, test/index.test.ts:699, and test/index.test.ts:1918.

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

Also applies to: 962-963, 2391-2393, 3514-3514

test/index.test.ts (1)

699-703: good regression updates for extracted index behavior.

the new assertions pin per-family propagation and empty-map reset behavior, and the status check avoids brittle exact-time assertions.

references: test/index.test.ts:699, test/index.test.ts:739, test/index.test.ts:811, test/index.test.ts:1918.

As per coding guidelines, test/**: "tests must stay deterministic and use vitest."

Also applies to: 739-753, 806-813, 1918-1922

test/active-index.test.ts (1)

9-118: solid baseline coverage for shared active-index helpers.

these tests lock the important normalization paths (nan, bounds, clear/fill empty semantics) and match the extracted helper surface.

references: test/active-index.test.ts:56, test/active-index.test.ts:80, test/active-index.test.ts:101.

lib/codex-manager.ts (6)

25-33: imports look correct; helpers are now centralized.

the new imports from lib/accounts/account-view.js and lib/accounts/active-index.js align with the extracted utilities. no token-handling or filesystem code introduced here.


1338-1338: usage of createActiveIndexByFamily in persistAccountPool is correct.

this replaces inline object construction and produces Partial<Record<ModelFamily, number>> as expected. the helper iterates MODEL_FAMILIES internally, so all families receive the same index.


2348-2355: createEmptyAccountStorage correctly initializes per-family indexes.

using createActiveIndexByFamily(0) ensures the empty storage starts with all families pointing to index 0, matching the previous inline behavior.


3053-3055: normalizeDoctorIndexes delegates to the shared helper.

normalizeActiveIndexByFamily(storage, storage.accounts.length) handles nan normalization, out-of-bounds clamping, and empty-account-map handling. the helper returns a boolean indicating whether mutations occurred, which is the expected return type here.


3614-3614: setActiveIndexForAllFamilies(storage, 0) after account deletion.

resetting to index 0 when an account is deleted is consistent with prior behavior. the helper mutates activeIndex and each family entry in-place.


3835-3835: setActiveIndexForAllFamilies(storage, targetIndex) in runSwitch.

the switch command now uses the shared helper to update the global and per-family indexes atomically. this is behavior-preserving and removes ~5 lines of inline duplication.

lib/codex-cli/sync.ts (3)

6-10: imports updated to use shared active-index helpers.

removing MODEL_FAMILIES and importing setActiveIndexForAllFamilies/normalizeActiveIndexByFamily from lib/accounts/active-index.js is consistent with the refactor. the type-only import for ModelFamily is retained for getActiveSelectionForFamily:line 354.


165-170: writeFamilyIndexes now delegates to the shared helper.

setActiveIndexForAllFamilies(storage, index) performs the same mutations: sets storage.activeIndex, initializes activeIndexByFamily if missing, and writes each family. this is behavior-preserving.

one consideration: writeFamilyIndexes is now a single-line wrapper. if no other callers exist, you could inline it at lib/codex-cli/sync.ts:303 and lib/codex-cli/sync.ts:314 (via normalizeStoredFamilyIndexes flow). not blocking, just a follow-up simplification.


187-189: normalizeStoredFamilyIndexes delegates to normalizeActiveIndexByFamily.

the helper handles:

  • clamping activeIndex to valid range
  • nan fallback to storage.activeIndex
  • per-family index normalization

the options param defaults correctly (no clearFamilyMapWhenEmpty), matching the old inline semantics. the docstring above (lines 172-186) documents concurrency and windows filesystem considerations, which satisfies the lib/** guideline for citing edge cases.

ndycode and others added 3 commits March 4, 2026 06:33
Move active-index-by-family label rendering into shared account-view helpers and reuse it in both v2 and legacy codex status output paths.

Add helper tests to lock 1-based labels and placeholder output for missing families.

Co-authored-by: Codex <noreply@openai.com>
Move codex-remove index/family reindexing into shared active-index helpers to centralize the mutation policy and reduce inline branching in the plugin entrypoint.

Add helper tests for in-range and out-of-range removal behavior.

Co-authored-by: Codex <noreply@openai.com>
Introduce shared account-storage view helpers for creating empty v3 storage and cloning mutable working copies, then reuse them across plugin login/check flow, codex-cli sync, and manager reset paths.

Add unit tests to lock clone isolation and family-index initialization behavior.

Co-authored-by: Codex <noreply@openai.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
index.ts (1)

955-968: ⚠️ Potential issue | 🟠 Major

coerce event index to an integer before persisting selection.

this path accepts any numeric value from event properties, so 1.7 can pass bounds checks and then be written through lib/accounts/active-index.ts:33-42. please normalize to an integer before validation and add a regression in test/index.test.ts for fractional selection events.

proposed fix
-const index = props.index ?? props.accountIndex;
-if (typeof index === "number") {
+const rawIndex = props.index ?? props.accountIndex;
+const index =
+	typeof rawIndex === "number" && Number.isFinite(rawIndex)
+		? Math.trunc(rawIndex)
+		: Number.NaN;
+if (Number.isInteger(index)) {
 	const storage = await loadAccounts();
 	if (!storage || index < 0 || index >= storage.accounts.length) {
 		return;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@index.ts` around lines 955 - 968, The event index (props.index or
props.accountIndex) must be coerced to an integer before validation and
persistence: read the raw value from props.index ?? props.accountIndex, convert
it using Math.trunc or Number.parseInt (ensuring NaN is handled), then perform
the bounds checks against storage.accounts.length and use that integer when
updating account.lastUsed/lastSwitchReason and calling
setActiveIndexForAllFamilies; also add a regression test in test/index.test.ts
that sends a fractional index (e.g., 1.7) and asserts the stored/active index is
the truncated integer (1) and out-of-range fractional values are rejected.
♻️ Duplicate comments (3)
lib/accounts/active-index.ts (1)

17-20: ⚠️ Potential issue | 🟠 Major

normalize indices to finite integers before writing state.

lib/accounts/active-index.ts:17-20 and lib/accounts/active-index.ts:38-42 still persist fractional/non-finite values. this can poison activeIndex and family maps with NaN/decimals. please normalize at the helper boundary and add vitest regressions in test/active-index.test.ts.

proposed fix
 function clampIndex(index: number, count: number): number {
 	if (count <= 0) return 0;
-	return Math.max(0, Math.min(index, count - 1));
+	const normalized = Number.isFinite(index) ? Math.trunc(index) : 0;
+	return Math.max(0, Math.min(normalized, count - 1));
 }
@@
 ): void {
-	storage.activeIndex = index;
+	const normalized = Number.isFinite(index) ? Math.max(0, Math.trunc(index)) : 0;
+	storage.activeIndex = normalized;
 	storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
 	for (const family of families) {
-		storage.activeIndexByFamily[family] = index;
+		storage.activeIndexByFamily[family] = normalized;
 	}
 }

As per coding guidelines, "lib/**: 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."

Also applies to: 33-42

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/accounts/active-index.ts` around lines 17 - 20, clampIndex currently
allows fractional or non-finite numbers to pass through; normalize inputs at the
helper boundary by coercing index and count to finite integers (e.g., via
Number.isFinite check and Math.floor or Math.trunc) inside clampIndex and the
analogous helper used around lines ~38-42 so activeIndex/state maps never
receive NaN or decimals; add vitest cases in test/active-index.test.ts covering
NaN, Infinity, negative decimals and fractional indices to assert they are
clamped to valid integer bounds and that non-finite inputs default safely.
lib/accounts/account-view.ts (2)

37-40: ⚠️ Potential issue | 🟠 Major

filter non-finite reset timestamps before selecting minimum.

lib/accounts/account-view.ts:37-40 accepts NaN/Infinity/-Infinity because it only checks typeof value === "number". that can propagate malformed wait labels through lib/accounts/account-view.ts:54-70. add finite checks and regressions in test/account-view.test.ts for these values.

proposed fix
 for (const [key, value] of Object.entries(times)) {
-	if (typeof value !== "number") continue;
+	if (typeof value !== "number" || !Number.isFinite(value)) continue;
 	if (value <= now) continue;
 	if (key !== family && !key.startsWith(prefix)) continue;

As per coding guidelines, "lib/**: 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."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/accounts/account-view.ts` around lines 37 - 40, The loop that filters
entries from the times object (checking typeof value === "number") should also
exclude non-finite numbers; update the condition in the for (const [key, value]
of Object.entries(times)) block to skip values where !Number.isFinite(value) in
addition to the existing checks for now and key/prefix/family, and ensure the
subsequent logic that computes the minimum wait (the code around the current
selection/aggregation used later in this module) will only see finite numbers;
add vitest regressions in test/account-view.test.ts that insert NaN, Infinity,
and -Infinity into the times map and assert they are ignored (no malformed wait
labels produced) and that valid finite timestamps still determine behavior.

22-24: ⚠️ Potential issue | 🟠 Major

normalize active index to an integer before clamping.

lib/accounts/account-view.ts:22-24 still allows fractional values (for example 1.7), which can lead to invalid array access in downstream callers. please coerce to an integer before clamp and add regression coverage in test/account-view.test.ts.

proposed fix
 const rawCandidate = storage.activeIndexByFamily?.[family] ?? storage.activeIndex;
-const raw = Number.isFinite(rawCandidate) ? rawCandidate : 0;
+const raw = Number.isFinite(rawCandidate) ? Math.trunc(rawCandidate) : 0;
 return Math.max(0, Math.min(raw, total - 1));

As per coding guidelines, "lib/**: 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."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/accounts/account-view.ts` around lines 22 - 24, The value rawCandidate
(from storage.activeIndexByFamily?.[family] ?? storage.activeIndex) must be
coerced to an integer before clamping to avoid fractional indices: update the
computation of raw to use an integer coercion (e.g., Math.trunc or Math.floor)
only when Number.isFinite(rawCandidate) is true, falling back to 0 otherwise,
then apply the existing Math.max/Math.min clamp; update the logic that assigns
raw (symbol names: rawCandidate and raw in account-view.ts) and add a vitest
regression in test/account-view.test.ts that passes a fractional active index
(e.g., 1.7) and asserts it is truncated and clamped correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/accounts/active-index.ts`:
- Around line 93-95: The range check around targetIndex in
lib/accounts/active-index.ts currently allows non-integer values (e.g., 1.2)
which splice will coerce; update the guard in the function that computes/removes
by index (the targetIndex check) to first reject non-integers using
Number.isInteger(targetIndex) before the existing bounds check, returning false
for non-integer or out-of-range values, and add a regression test in
test/active-index.test.ts that calls the same removal/path with a non-integer
index (e.g., 1.2) and asserts no account was removed (false return or unchanged
array) to prevent future regressions.

In `@lib/accounts/storage-view.ts`:
- Line 23: The current accounts mapping in storage-view.ts uses a shallow copy
(accounts: storage.accounts.map((account) => ({ ...account }))) which leaves
nested mutable fields shared; replace that shallow clone with a deep-clone of
each account so nested objects/maps/arrays are duplicated (manually clone
specific mutable fields or use a safe deep-copy helper) and ensure any Map/Set
are re-created rather than copied by reference; update the test at
test/account-storage-view.test.ts (around lines 25-41) to assert that mutating
nested fields on the returned view does not change the original
storage.accounts, and add coverage for nested Map/Set/array mutation cases.

In `@lib/codex-manager.ts`:
- Around line 3588-3590: clearAccountsAndReset currently calls
createEmptyAccountStorageBase directly which bypasses the local wrapper used
elsewhere and risks diverging defaults; change clearAccountsAndReset to build
the empty storage via the same local wrapper used at the earlier call site (the
project’s empty-storage constructor wrapper) and pass that result to
saveAccounts instead of calling createEmptyAccountStorageBase directly so
shape/defaults remain consistent (i.e., replace the
createEmptyAccountStorageBase usage in clearAccountsAndReset with the local
wrapper used elsewhere, then call saveAccounts with that value).
- Around line 3605-3607: The deletion currently calls
setActiveIndexForAllFamilies(storage, 0) which resets every family's active
index to 0; instead update only families affected by the removed account:
compute removedIdx (the idx being spliced) and for each family in
storage.families, if family.activeIndex === removedIdx set it to
Math.min(removedIdx, storage.accounts.length - 1) (or 0 if no accounts remain),
else if family.activeIndex > removedIdx decrement it by 1; then call
saveAccounts(storage). Modify the call site (where storage.accounts.splice(idx,
1) occurs) or extend setActiveIndexForAllFamilies to accept removedIdx and apply
this logic so only impacted families' active indices change.
- Line 3589: Add vitest regression tests in test/codex-manager-cli.test.ts that
exercise manager-level flows: call clearAccountsAndReset and verify family-index
initialization and persistence across storage restarts; simulate delete actions
via handleManageAction (which invokes setActiveIndexForAllFamilies) and assert
correct reindexing when the active selection becomes invalid; run runSwitch
(which also uses setActiveIndexForAllFamilies) concurrently with delete and with
simulated EBUSY file-write contention to validate retry mechanics and ordering
against persisted state; use helpers to orchestrate concurrent operations and
persistence reloads to confirm invariants hold across process/storage
boundaries.

In `@test/account-storage-view.test.ts`:
- Around line 25-41: Add a nested-field mutation assertion to the existing clone
isolation test by extending the original object with a nested mutable structure
(e.g., a rateLimits map or nested object) and then verifying after calling
cloneAccountStorage that mutating the nested field on the clone does not change
the original; specifically update the test that uses cloneAccountStorage (from
lib/accounts/storage-view.ts) to include a nested map under accounts[0] or
top-level (e.g., rateLimits: { window: { limit: 10 } }), clone it, mutate the
nested value on the clone, and add an expect asserting the original's nested
value is unchanged to ensure cloneAccountStorage performs a deep (not shallow)
copy.

In `@test/account-view.test.ts`:
- Around line 21-35: Add deterministic regression tests in
test/account-view.test.ts that assert resolveActiveIndex correctly coerces
fractional activeIndex values (e.g., 1.5 or Number.POSITIVE_INFINITY) down to a
valid integer index and falls back to storage/length when given NaN, and also
add tests that pass non-finite values (Number.NaN, Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY) into the rateLimitResetTimes input exercised by the
account view functions so the code paths in lib/accounts/account-view.ts (the
normalization around lines handling activeIndex and the branch that normalizes
rateLimitResetTimes) are covered and assert the normalized/reset behavior
remains enforced.

In `@test/active-index.test.ts`:
- Around line 57-79: Add deterministic regression tests that pass non-finite and
fractional values into normalizeActiveIndexByFamily and into the routines that
set active indexes for all families to ensure the sanitization logic in
normalizeActiveIndexByFamily (lib/accounts/active-index.ts around the function
normalizeActiveIndexByFamily) and the removal/normalization code path (the logic
referenced near lines 93-95 in lib/accounts/active-index.ts that handles
targetIndex removal) correctly coerce NaN, Infinity, negative fractions and
non-integer targetIndex to integer bounds. Specifically, extend
test/active-index.test.ts to include cases where storage.activeIndex and entries
in activeIndexByFamily are NaN, Infinity, negative fractions, and where
setActiveIndexForAllFamilies or the remove-by-targetIndex code is given
fractional/non-integer targetIndex, then assert that returned changed flags,
storage.activeIndex, and each storage.activeIndexByFamily[family] are finite
integers within [0, upperBound-1] (and that removal uses Math.floor or
equivalent integer coercion), so the functions referenced
(normalizeActiveIndexByFamily and the targetIndex removal logic) are exercised
for these edge inputs.

---

Outside diff comments:
In `@index.ts`:
- Around line 955-968: The event index (props.index or props.accountIndex) must
be coerced to an integer before validation and persistence: read the raw value
from props.index ?? props.accountIndex, convert it using Math.trunc or
Number.parseInt (ensuring NaN is handled), then perform the bounds checks
against storage.accounts.length and use that integer when updating
account.lastUsed/lastSwitchReason and calling setActiveIndexForAllFamilies; also
add a regression test in test/index.test.ts that sends a fractional index (e.g.,
1.7) and asserts the stored/active index is the truncated integer (1) and
out-of-range fractional values are rejected.

---

Duplicate comments:
In `@lib/accounts/account-view.ts`:
- Around line 37-40: The loop that filters entries from the times object
(checking typeof value === "number") should also exclude non-finite numbers;
update the condition in the for (const [key, value] of Object.entries(times))
block to skip values where !Number.isFinite(value) in addition to the existing
checks for now and key/prefix/family, and ensure the subsequent logic that
computes the minimum wait (the code around the current selection/aggregation
used later in this module) will only see finite numbers; add vitest regressions
in test/account-view.test.ts that insert NaN, Infinity, and -Infinity into the
times map and assert they are ignored (no malformed wait labels produced) and
that valid finite timestamps still determine behavior.
- Around line 22-24: The value rawCandidate (from
storage.activeIndexByFamily?.[family] ?? storage.activeIndex) must be coerced to
an integer before clamping to avoid fractional indices: update the computation
of raw to use an integer coercion (e.g., Math.trunc or Math.floor) only when
Number.isFinite(rawCandidate) is true, falling back to 0 otherwise, then apply
the existing Math.max/Math.min clamp; update the logic that assigns raw (symbol
names: rawCandidate and raw in account-view.ts) and add a vitest regression in
test/account-view.test.ts that passes a fractional active index (e.g., 1.7) and
asserts it is truncated and clamped correctly.

In `@lib/accounts/active-index.ts`:
- Around line 17-20: clampIndex currently allows fractional or non-finite
numbers to pass through; normalize inputs at the helper boundary by coercing
index and count to finite integers (e.g., via Number.isFinite check and
Math.floor or Math.trunc) inside clampIndex and the analogous helper used around
lines ~38-42 so activeIndex/state maps never receive NaN or decimals; add vitest
cases in test/active-index.test.ts covering NaN, Infinity, negative decimals and
fractional indices to assert they are clamped to valid integer bounds and that
non-finite inputs default safely.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0d96ab3 and 6b7b2e8.

📒 Files selected for processing (9)
  • index.ts
  • lib/accounts/account-view.ts
  • lib/accounts/active-index.ts
  • lib/accounts/storage-view.ts
  • lib/codex-cli/sync.ts
  • lib/codex-manager.ts
  • test/account-storage-view.test.ts
  • test/account-view.test.ts
  • test/active-index.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)
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/active-index.test.ts
  • test/account-view.test.ts
  • test/account-storage-view.test.ts
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-cli/sync.ts
  • lib/accounts/storage-view.ts
  • lib/accounts/account-view.ts
  • lib/accounts/active-index.ts
  • lib/codex-manager.ts
🧬 Code graph analysis (7)
test/active-index.test.ts (2)
lib/accounts/active-index.ts (4)
  • createActiveIndexByFamily (22-31)
  • setActiveIndexForAllFamilies (33-43)
  • normalizeActiveIndexByFamily (45-86)
  • removeAccountAndReconcileActiveIndexes (88-124)
lib/prompts/codex.ts (1)
  • MODEL_FAMILIES (58-64)
lib/codex-cli/sync.ts (2)
lib/accounts/active-index.ts (2)
  • setActiveIndexForAllFamilies (33-43)
  • normalizeActiveIndexByFamily (45-86)
lib/accounts/storage-view.ts (2)
  • cloneAccountStorage (20-29)
  • createEmptyAccountStorage (8-18)
lib/accounts/storage-view.ts (1)
lib/accounts/active-index.ts (1)
  • createActiveIndexByFamily (22-31)
test/account-view.test.ts (1)
lib/accounts/account-view.ts (5)
  • resolveActiveIndex (16-25)
  • getRateLimitResetTimeForFamily (27-47)
  • formatRateLimitEntry (49-59)
  • formatRateLimitStatusByFamily (61-71)
  • formatActiveIndexByFamilyLabels (73-82)
test/account-storage-view.test.ts (2)
lib/accounts/storage-view.ts (2)
  • createEmptyAccountStorage (8-18)
  • cloneAccountStorage (20-29)
lib/prompts/codex.ts (1)
  • MODEL_FAMILIES (58-64)
lib/accounts/active-index.ts (1)
lib/prompts/codex.ts (2)
  • ModelFamily (47-52)
  • MODEL_FAMILIES (58-64)
index.ts (3)
lib/accounts/active-index.ts (3)
  • setActiveIndexForAllFamilies (33-43)
  • normalizeActiveIndexByFamily (45-86)
  • removeAccountAndReconcileActiveIndexes (88-124)
lib/accounts/storage-view.ts (2)
  • cloneAccountStorage (20-29)
  • createEmptyAccountStorage (8-18)
lib/accounts/account-view.ts (2)
  • formatActiveIndexByFamilyLabels (73-82)
  • formatRateLimitStatusByFamily (61-71)
🔇 Additional comments (2)
lib/codex-cli/sync.ts (1)

149-154: good helper consolidation in sync flow.

lib/codex-cli/sync.ts:149-154, lib/codex-cli/sync.ts:171-173, and lib/codex-cli/sync.ts:255 cleanly delegate to shared helpers, which reduces duplication and drift across reconcile paths. coverage in test/active-index.test.ts and test/account-storage-view.test.ts aligns with this extraction.

Also applies to: 171-173, 255-255

lib/codex-manager.ts (1)

25-36: good extraction and reuse of shared account-index helpers.

the wiring at lib/codex-manager.ts:25-36, lib/codex-manager.ts:1341, lib/codex-manager.ts:2352, lib/codex-manager.ts:3051, and lib/codex-manager.ts:3827 cleanly removes local duplication without introducing new token/email logging paths.

Also applies to: 1341-1341, 2352-2352, 3051-3051, 3827-3827

…conciliation

- normalize event and helper indexes to safe integer bounds\n- ignore non-finite rate-limit reset timestamps\n- deep-clone account rate-limit maps in storage views\n- reconcile active selection when deleting accounts instead of forcing index 0\n- add regression coverage for fractional/non-finite inputs and manager delete flow\n\nCo-authored-by: Codex <noreply@openai.com>
Update the mocked model-family set and deletion assertion so the test validates that non-codex family indexes are reindexed correctly after account removal.\n\nCo-authored-by: Codex <noreply@openai.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
index.ts (1)

955-971: ⚠️ Potential issue | 🟠 Major

make account selection writes atomic to avoid lost updates.

index.ts:955-971 does a load-mutate-save sequence without transaction boundaries. two concurrent selection events can overwrite each other or race with other writers.

proposed fix
- const storage = await loadAccounts();
- if (!storage || index < 0 || index >= storage.accounts.length) {
-   return;
- }
- const now = Date.now();
- const account = storage.accounts[index];
- if (account) {
-   account.lastUsed = now;
-   account.lastSwitchReason = "rotation";
- }
- setActiveIndexForAllFamilies(storage, index);
- await saveAccounts(storage);
+ await withAccountStorageTransaction(async (storage, persist) => {
+   if (!storage || index < 0 || index >= storage.accounts.length) return;
+   const now = Date.now();
+   const account = storage.accounts[index];
+   if (account) {
+     account.lastUsed = now;
+     account.lastSwitchReason = "rotation";
+   }
+   setActiveIndexForAllFamilies(storage, index);
+   await persist(storage);
+ });

please add a vitest regression that fires two concurrent account.select events and asserts final persisted selection is consistent.
As per coding guidelines, "lib/**: 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."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@index.ts` around lines 955 - 971, The load-mutate-save sequence in the
account selection handler (the block using loadAccounts, modifying
account.lastUsed/lastSwitchReason, calling setActiveIndexForAllFamilies, then
saveAccounts) is not atomic and can be lost to races; change it to use a
per-accounts lock/queue or file-level atomic update (e.g., acquire a mutex or
enqueue writes before calling loadAccounts/saveAccounts) so concurrent
account.select handlers serialize updates and retry on transient EBUSY/429
errors, preserving last-used/active-index changes; update the handler that
processes the account.select event to obtain the lock, re-load storage inside
the lock, validate index, mutate, and call saveAccounts before releasing the
lock, and ensure logging during the flow never prints tokens or emails. Add a
vitest regression that fires two concurrent account.select events (calling the
same handler) and asserts the final persisted storage (via loadAccounts) has a
single consistent active index and both updates applied deterministically;
reference the functions loadAccounts, saveAccounts, setActiveIndexForAllFamilies
and the account.select event handler in the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@index.ts`:
- Around line 2398-2400: The clamp-only call to normalizeActiveIndexByFamily is
causing per-family active pointers to drift on deletions; replace that
clamp-only post-processing with the reindexing/reconciliation logic used in
lib/accounts/active-index.ts (the logic around lines 94-139) so families whose
active index should shift left when an earlier account is removed are updated
correctly (i.e., use the reconcile/reindex function or inline its algorithm
instead of plain clamping) and then add a vitest regression that constructs
activeIndexByFamily like { codex: 2, "gpt-5.1": 1 }, deletes index 0, runs the
same normalization, and asserts both family selections are preserved correctly
after reindexing; reference normalizeActiveIndexByFamily and activeIndexByFamily
in your changes and ensure the new test is named to indicate deletion reindex
regression.

In `@lib/codex-manager.ts`:
- Around line 3607-3609: The delete flow currently calls
removeAccountAndReconcileActiveIndexes(idx) then unconditionally calls
saveAccounts(storage) and console.log; change it to check the boolean result
returned by removeAccountAndReconcileActiveIndexes (and/or deleteAccountIndex
wrapper) and only call saveAccounts(storage) and print the "Deleted..." message
when the result is true (i.e., an actual removal occurred); if false, do not
persist or log success and instead return/throw as appropriate. Add a vitest
regression that calls manage-mode deleteAccountIndex with a fractional index,
asserting that saveAccounts was not invoked, no success log was emitted, and
storage remains unchanged. Ensure the fix references
removeAccountAndReconcileActiveIndexes and saveAccounts so tests and logging
behavior are updated accordingly.

---

Outside diff comments:
In `@index.ts`:
- Around line 955-971: The load-mutate-save sequence in the account selection
handler (the block using loadAccounts, modifying
account.lastUsed/lastSwitchReason, calling setActiveIndexForAllFamilies, then
saveAccounts) is not atomic and can be lost to races; change it to use a
per-accounts lock/queue or file-level atomic update (e.g., acquire a mutex or
enqueue writes before calling loadAccounts/saveAccounts) so concurrent
account.select handlers serialize updates and retry on transient EBUSY/429
errors, preserving last-used/active-index changes; update the handler that
processes the account.select event to obtain the lock, re-load storage inside
the lock, validate index, mutate, and call saveAccounts before releasing the
lock, and ensure logging during the flow never prints tokens or emails. Add a
vitest regression that fires two concurrent account.select events (calling the
same handler) and asserts the final persisted storage (via loadAccounts) has a
single consistent active index and both updates applied deterministically;
reference the functions loadAccounts, saveAccounts, setActiveIndexForAllFamilies
and the account.select event handler in the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 46e6b32d-4da1-4008-a6e9-e8afcfd3a44c

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7b2e8 and 3135264.

📒 Files selected for processing (10)
  • index.ts
  • lib/accounts/account-view.ts
  • lib/accounts/active-index.ts
  • lib/accounts/storage-view.ts
  • lib/codex-manager.ts
  • test/account-storage-view.test.ts
  • test/account-view.test.ts
  • test/active-index.test.ts
  • test/codex-manager-cli.test.ts
  • test/index.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)
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/account-view.test.ts
  • test/index.test.ts
  • test/account-storage-view.test.ts
  • test/codex-manager-cli.test.ts
  • test/active-index.test.ts
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/accounts/account-view.ts
  • lib/accounts/active-index.ts
  • lib/accounts/storage-view.ts
  • lib/codex-manager.ts
🧬 Code graph analysis (8)
test/account-view.test.ts (1)
lib/accounts/account-view.ts (5)
  • resolveActiveIndex (16-25)
  • getRateLimitResetTimeForFamily (27-47)
  • formatRateLimitEntry (49-59)
  • formatRateLimitStatusByFamily (61-71)
  • formatActiveIndexByFamilyLabels (73-82)
test/index.test.ts (1)
lib/prompts/codex.ts (1)
  • MODEL_FAMILIES (58-64)
lib/accounts/account-view.ts (1)
lib/prompts/codex.ts (2)
  • ModelFamily (47-52)
  • MODEL_FAMILIES (58-64)
lib/accounts/active-index.ts (1)
lib/prompts/codex.ts (2)
  • ModelFamily (47-52)
  • MODEL_FAMILIES (58-64)
lib/accounts/storage-view.ts (1)
lib/accounts/active-index.ts (1)
  • createActiveIndexByFamily (27-36)
test/codex-manager-cli.test.ts (2)
lib/codex-manager.ts (1)
  • runCodexMultiAuthCli (3966-4027)
lib/accounts/account-view.ts (1)
  • formatRateLimitEntry (49-59)
lib/codex-manager.ts (2)
lib/accounts/active-index.ts (4)
  • createActiveIndexByFamily (27-36)
  • normalizeActiveIndexByFamily (51-92)
  • removeAccountAndReconcileActiveIndexes (94-139)
  • setActiveIndexForAllFamilies (38-49)
lib/accounts/storage-view.ts (1)
  • createEmptyAccountStorage (8-18)
index.ts (3)
lib/accounts/active-index.ts (3)
  • setActiveIndexForAllFamilies (38-49)
  • normalizeActiveIndexByFamily (51-92)
  • removeAccountAndReconcileActiveIndexes (94-139)
lib/accounts/storage-view.ts (2)
  • cloneAccountStorage (20-36)
  • createEmptyAccountStorage (8-18)
lib/accounts/account-view.ts (2)
  • formatActiveIndexByFamilyLabels (73-82)
  • formatRateLimitStatusByFamily (61-71)
🔇 Additional comments (7)
lib/accounts/active-index.ts (1)

17-25: good normalization and reconcile boundaries.

lib/accounts/active-index.ts:17-25 and lib/accounts/active-index.ts:94-139 now enforce finite integer handling and safe delete reconciliation. this closes the fractional/non-finite and splice-coercion risk paths cleanly.

Also applies to: 94-139

lib/accounts/storage-view.ts (1)

20-35: clone isolation fix looks correct.

lib/accounts/storage-view.ts:20-35 correctly isolates rateLimitResetTimes while cloning accounts, so nested mutations on clones do not leak back.

lib/accounts/account-view.ts (1)

16-25: active index and rate-limit parsing hardening is solid.

lib/accounts/account-view.ts:16-25 and lib/accounts/account-view.ts:27-47 correctly floor/clamp indexes and ignore non-finite reset timestamps before min-selection.

Also applies to: 27-47

test/account-storage-view.test.ts (1)

25-50: good deterministic regression for nested clone isolation.

test/account-storage-view.test.ts:25-50 now validates top-level and nested mutation isolation (rateLimitResetTimes) with explicit assertions.

test/codex-manager-cli.test.ts (1)

502-529: new cli regressions are useful and deterministic.

test/codex-manager-cli.test.ts:502-529 and test/codex-manager-cli.test.ts:1974-2022 strengthen status and delete/reindex coverage under vitest with stable inputs.

Also applies to: 1974-2022

test/account-view.test.ts (1)

21-52: coverage here is strong for the edge cases that mattered.

test/account-view.test.ts:21-52 and test/account-view.test.ts:72-102 lock empty/fractional/non-finite index behavior and non-finite reset timestamp filtering in deterministic vitest cases.

Also applies to: 72-102

test/active-index.test.ts (1)

37-56: good regression coverage for index sanitization and removal guards.

test/active-index.test.ts:37, test/active-index.test.ts:78, and test/active-index.test.ts:182 lock finite-integer coercion, bounds clamping, and non-integer deletion rejection in a deterministic vitest suite.

Also applies to: 78-101, 182-197

- use removal reconciliation (not clamp-only) in plugin delete flows to keep per-family selection stable
- guard manage delete persistence/logging on actual removal result
- align index test model-family mock with production families and add multi-family reindex regression
- add codex-manager CLI regression for fractional delete index handling

Co-authored-by: Codex <noreply@openai.com>
- normalize activeIndexByFamily after codex-remove reconciliation to align with other delete paths
- add regression for removal when activeIndexByFamily is undefined

Co-authored-by: Codex <noreply@openai.com>
Ensure removeAccountAndReconcileActiveIndexes initializes and updates activeIndexByFamily even when legacy storage omits that field. Add regression coverage for deletion from legacy-shaped storage.

Co-authored-by: Codex <noreply@openai.com>
Keep cloneAccountStorage from normalizing undefined activeIndexByFamily to an empty object so legacy-shaped snapshots do not trigger spurious changed-state signals. Add regression coverage.

Co-authored-by: Codex <noreply@openai.com>
@ndycode
Copy link
Owner Author

ndycode commented Mar 6, 2026

Merged via integration PR to safely combine multiple approved changes.

@ndycode ndycode closed this Mar 6, 2026
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.

1 participant