Skip to content

fix(auth): define reset and delete safety flows#75

Open
ndycode wants to merge 12 commits intomainfrom
git-plan/01-reset-safety
Open

fix(auth): define reset and delete safety flows#75
ndycode wants to merge 12 commits intomainfrom
git-plan/01-reset-safety

Conversation

@ndycode
Copy link
Owner

@ndycode ndycode commented Mar 12, 2026

Summary

  • consolidate delete/reset behavior in lib/destructive-actions.ts and keep typed DELETE / RESET as the single destructive confirmation path for both TTY and fallback CLI flows
  • harden local-state cleanup against real Windows/storage races by waiting for in-flight menu quota refreshes before reset, retrying transient unlink failures, preserving rollback context on partial delete failures, and clearing legacy account artifacts
  • keep the menu and docs aligned with the shipped behavior by sanitizing auth-menu subtitle/search text, aligning destructive-action messaging, and updating troubleshooting guidance
  • add regression coverage for destructive-action concurrency, quota-cache reset ordering, Windows retry paths, and flagged-account/delete edge cases

Validation

  • npm run typecheck
  • npm run lint
  • npx vitest run --exclude test/oauth-server.integration.test.ts

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 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

adds a reset-local-state option and typed-confirm flows, introduces a destructive-actions module, wires delete/reset flows into cli/ui, and adds quota-cache clearing and robust file-retry helpers across storage. (50 words)

changes

Cohort / File(s) Summary
documentation
docs/troubleshooting.md
rename "Soft Reset" → "Reset Options"; add exact-effects table; update manual steps to remove account files and handle quota-cache.json (docs/troubleshooting.md:line).
destructive actions module
lib/destructive-actions.ts
new module exporting DESTRUCTIVE_ACTION_COPY, clampActiveIndices(), deleteAccountAtIndex(), deleteSavedAccounts(), resetLocalState(); handles saved accounts, flagged accounts, quota cache clearing and index rebasing (lib/destructive-actions.ts:line).
cli mode & prompt handling
lib/cli.ts
add "reset" to LoginMode; use typed confirms from DESTRUCTIVE_ACTION_COPY; add reset-all path and confirmation flow in interactive login (lib/cli.ts:line).
codex manager integration
lib/codex-manager.ts
add destructiveActionInFlight guard; handleManageAction accepts optional flaggedStorage?: FlaggedAccountStorageV1; replace direct clears with deleteAccountAtIndex, deleteSavedAccounts, resetLocalState calls (lib/codex-manager.ts:line).
storage & retries
lib/storage.ts
add unlinkWithRetry/renameFileWithRetry; change clearAccounts() and clearFlaggedAccounts() to return Promise<boolean> and use per-artifact retry/cleanup (lib/storage.ts:line).
quota cache
lib/quota-cache.ts
add clearQuotaCache(): Promise<boolean>, QUOTA_CACHE_FILE_NAME, and label helper; use retries and improved logging (lib/quota-cache.ts:line).
ui action types & menu wiring
lib/ui/auth-menu.ts
add { type: "reset-all" } to AuthMenuAction; inject reset option into danger area and wire focus/selection logic (lib/ui/auth-menu.ts:line).
ui copy
lib/ui/copy.ts
add UI_COPY.mainMenu.resetLocalState; rename "Delete All Accounts" → "Delete Saved Accounts"; add fallback option r in prompts (lib/ui/copy.ts:line).
index & exports
index.ts
replace clearAccounts/clearFlaggedAccounts usage with destructive-actions APIs; remove those exports and re-export/consume new functions and copy constants (index.ts:line).
tests
test/destructive-actions.test.ts, test/codex-manager-cli.test.ts, test/quota-cache.test.ts, test/storage-flagged.test.ts, test/*
add destructive-actions unit tests and mocks; extend CLI/auth-menu tests for reset flows; add quota-cache retry tests; update tests to expect boolean outcomes for clear* functions (test/destructive-actions.test.ts:line, test/codex-manager-cli.test.ts:line, test/quota-cache.test.ts:line).

sequence diagram(s)

sequenceDiagram
    participant user as user
    participant cli as cli
    participant menu as auth menu
    participant da as destructive actions
    participant storage as account storage
    participant flagged as flagged accounts
    participant quota as quota cache
    participant codex as codex cli state

    user->>cli: choose "reset-all"
    cli->>menu: show typed-confirm using DESTRUCTIVE_ACTION_COPY (`lib/cli.ts`:line)
    menu->>user: request typed confirmation (`lib/ui/auth-menu.ts`:line)
    user->>menu: confirm
    menu->>da: call resetLocalState() (`lib/destructive-actions.ts`:line)

    par remove saved accounts
        da->>storage: delete saved accounts / rebase indices (`lib/destructive-actions.ts`:line)
        storage->>storage: persist storage (`lib/storage.ts`:line)
    and remove flagged accounts
        da->>flagged: load flagged, filter by refreshToken, persist if changed (`lib/destructive-actions.ts`:line)
    and clear caches
        da->>quota: clearQuotaCache() (`lib/quota-cache.ts`:line)
        da->>codex: clear codex cli state (`lib/destructive-actions.ts`:line)
    end

    da-->>menu: completion message (`lib/destructive-actions.ts`:line)
    menu-->>user: display "reset complete" (`lib/ui/auth-menu.ts`:line)
Loading

estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

detailed review flags

  • missing regression tests: add focused tests for deleteAccountAtIndex() when invoked with and without the optional flaggedStorage to exercise on-demand flagged load and persistence (lib/destructive-actions.ts:line, lib/codex-manager.ts:line, test/codex-manager-cli.test.ts:line).
  • windows edge cases: verify clearQuotaCache() and all unlink/rename retries handle windows-specific errors (EACCES/EPERM/EBUSY) and path semantics; add CI/unit tests simulating locked-file/unlink failures on windows (lib/quota-cache.ts:line, lib/storage.ts:line, lib/destructive-actions.ts:line).
  • concurrency risk: destructive actions reclamp/rebase indices and persist; concurrent destructive operations can race and corrupt indices. ensure serialization/mutex around destructive actions and add tests simulating concurrent save+delete flows (lib/destructive-actions.ts:line, lib/codex-manager.ts:line, test/codex-manager-cli.test.ts:line).

suggested labels

bug

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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
Description check ✅ Passed pr description covers objectives, validation steps, and key technical changes. structure aligns with template requirements for summary, validation, and additional notes.
Title check ✅ Passed the title follows conventional commits format with type 'fix', scope 'auth', and a clear lowercase imperative summary under 72 chars that accurately describes the main change: defining reset and delete safety flows.

✏️ 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 git-plan/01-reset-safety
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/cli.ts">

<violation number="1" location="lib/cli.ts:213">
P2: Require typed confirmation for `reset` in fallback mode as well; currently it bypasses the destructive-action safety check.</violation>
</file>

<file name="lib/quota-cache.ts">

<violation number="1" location="lib/quota-cache.ts:274">
P2: `clearQuotaCache` should retry transient `EBUSY`/`EPERM` unlink failures; a single attempt can leave stale quota cache after reset/delete flows.</violation>
</file>

<file name="docs/troubleshooting.md">

<violation number="1" location="docs/troubleshooting.md:106">
P2: The manual reset/delete instructions are not equivalent to the CLI destructive actions because they omit account WAL/backup cleanup. Leaving `.wal`/`.bak*` artifacts can cause deleted accounts to be recovered on next load.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@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: 7

Caution

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

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

4120-4130: ⚠️ Potential issue | 🟡 Minor

potential stale flaggedStorage after delete operations.

flaggedStorage is loaded at lib/codex-manager.ts:4120 inside the menu loop, but after deleteAccountAtIndex modifies it at lib/codex-manager.ts:4022-4026, the next menu iteration may use stale data since the loop continues without reloading.

if deleteAccountAtIndex mutates the passed flaggedStorage object in place and persists it, subsequent iterations could show inconsistent counts in the menu (e.g., flaggedCount at line 4125).

#!/bin/bash
# check if deleteAccountAtIndex mutates flaggedStorage in place or returns new data
echo "=== checking deleteAccountAtIndex implementation ==="
rg -A 30 "export.*function deleteAccountAtIndex|export async function deleteAccountAtIndex" lib/

Also applies to: 4225-4240

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

In `@lib/codex-manager.ts` around lines 4120 - 4130, The menu uses flaggedStorage
loaded by loadFlaggedAccounts() but deleteAccountAtIndex may mutate/persist it
and the loop continues without reloading, causing stale flaggedCount shown to
promptLoginMode; fix by reloading or replacing flaggedStorage after any
deleteAccountAtIndex call (or change deleteAccountAtIndex to return the new
storage and assign it). Update the loop so that after calling
deleteAccountAtIndex you either call flaggedStorage = await
loadFlaggedAccounts() or assign the returned updated storage before continuing,
and ensure promptLoginMode/toExistingAccountInfo always receives the fresh
flaggedStorage for the flaggedCount.
test/codex-manager-cli.test.ts (1)

51-54: ⚠️ Potential issue | 🟠 Major

these cases do not exercise the new accept/cancel safety flow.

with promptLoginMode mocked at test/codex-manager-cli.test.ts:51-54 and the destructive-actions module mocked at test/codex-manager-cli.test.ts:138-157, the new tests at test/codex-manager-cli.test.ts:2161-2248 only verify how runCodexMultiAuthCli() reacts to a pre-baked result. they never hit the real confirm/cancel behavior in lib/cli.ts:173-235 or lib/ui/auth-menu.ts:777-788, which is the safety surface this pr is adding. please add focused vitest coverage against the real prompt/menu modules, including the fallback branch. As per coding guidelines, test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.

Also applies to: 138-157, 2161-2248

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

In `@test/codex-manager-cli.test.ts` around lines 51 - 54, The current tests mock
promptLoginMode and destructive-actions so they never exercise the real
accept/cancel safety flow; add focused Vitest tests that import the real
prompt/menu modules (do not vi.mock promptLoginMode or destructive-actions in
these new tests) and call runCodexMultiAuthCli to drive the confirm/cancel
branches in lib/cli.ts and the accept/cancel + fallback in lib/ui/auth-menu.ts,
asserting both acceptance and cancellation behavior. Use Vitest utilities (fake
timers, controlled async helpers) to keep tests deterministic and add targeted
regression scenarios that simulate concurrency/token-refresh races by
orchestrating parallel promises around runCodexMultiAuthCli and stub the token
refresh entry points, and simulate Windows filesystem behavior by stubbing
fs/path functions (e.g., path.sep or fs.stat) to trigger the Windows branch;
keep tests isolated and use vitest mocks/stubs only for external side effects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/troubleshooting.md`:
- Around line 106-129: The troubleshooting doc's manual cleanup commands omit
the write-ahead log and backup/recovery files that clearAccounts() in
lib/storage.ts removes, so update docs/troubleshooting.md to either list and
include removal commands for the WAL and backup-recovery artifacts (matching
clearAccounts() behavior) or replace the manual steps with a clear instruction
to run the CLI destructive command instead; reference clearAccounts(), the
existing JSON filenames (openai-codex-accounts.json,
openai-codex-flagged-accounts.json, quota-cache.json), and mention the
WAL/backup-recovery artifacts so the doc text and commands exactly mirror the
CLI behavior.

In `@lib/cli.ts`:
- Around line 205-214: The fallback readline path in promptLoginModeFallback is
returning "fresh" or "reset" immediately (see the branch that returns { mode:
"fresh", deleteAll: true } and { mode: "reset" }) which bypasses the
typed-confirm destructive guard implemented in promptLoginMode (the
DESTRUCTIVE_ACTION_COPY flow used in the normal TTY path). Update
promptLoginModeFallback to invoke the same DESTRUCTIVE_ACTION_COPY confirm flow
(reusing the promptLoginMode or the underlying confirm routine) so non-TTY users
must explicitly type the confirm text to proceed, and make sure the user-facing
wording matches lib/ui/copy.ts semantics (clarify delete vs reset). Add vitest
coverage for the fallback path (unmock promptLoginMode in
test/codex-manager-cli.test.ts and add tests to cover both accept and cancel
outcomes) and update tests referenced in the review to assert both branches.

In `@lib/codex-manager.ts`:
- Around line 4195-4219: Add a re-entry guard around destructive operations so
deleteSavedAccounts() and resetLocalState() cannot run concurrently: introduce a
module-level boolean (e.g., isDestructiveInFlight) and check it before invoking
runActionPanel; if true, skip or show a message. When running runActionPanel for
DESTRUCTIVE_ACTION_COPY.deleteSavedAccounts or
DESTRUCTIVE_ACTION_COPY.resetLocalState, set isDestructiveInFlight = true before
starting the async action and reset it in a finally block after the await
completes (or fails) to ensure the flag is always cleared; alternatively
integrate a simple mutex/lock helper used by the callers to serialize these
actions and/or ensure runActionPanel exposes an option to block input while the
action runs. Ensure references include runActionPanel, deleteSavedAccounts,
resetLocalState, DESTRUCTIVE_ACTION_COPY, and displaySettings so the change is
applied at the correct call sites.
- Around line 4028-4034: The console.log in codex-manager.ts leaks full emails
because it calls formatAccountLabel (from lib/accounts.ts) which embeds the raw
email; update the logging to use a masked email variant instead of the raw one:
either modify formatAccountLabel to return a safe/masked string for log usage or
add a new helper (e.g., maskEmail or formatAccountLabelForLogs) and use that
when building the label for deleted.removedAccount in the Deleted log path (the
code that reads deleted.removedAccount and deleted.removedFlaggedCount); ensure
masking keeps enough context (e.g., first char and domain or last 6 chars) but
never logs the full address.

In `@lib/destructive-actions.ts`:
- Around line 38-69: clampActiveIndices currently only clamps values which
causes deleting an account before the current one to leave stored indices
pointing one-too-high; before calling clampActiveIndices you must decrement
storage.activeIndex and every entry in storage.activeIndexByFamily that is
greater than the removed index (the deleted position) so indices rebase
correctly, then call clampActiveIndices(storage) to bound them; update logic
around the removal/splice site that deletes an account to perform this decrement
(referencing storage.activeIndex, storage.activeIndexByFamily and
MODEL_FAMILIES/AccountStorageV3) and add a Vitest that deletes an account at a
lower index than the active one and asserts activeIndex and each
activeIndexByFamily entry were shifted down (replace the current mock of this
module in the test with a real-module exercise so the regression is caught).

In `@lib/ui/auth-menu.ts`:
- Around line 97-102: sanitizeTerminalText currently embeds the control char
\u001b in a regex literal which triggers the noControlCharactersInRegex lint
rule; change the ANSI escape regex to be built via the RegExp constructor using
a string (e.g. new RegExp("\\u001b\\[[0-?]*[ -/]*[`@-`~]", "g")) and keep the
subsequent control-character removal and trim logic intact in
sanitizeTerminalText so behavior is unchanged. After fixing the regex, add
Vitest regression tests for sanitizeTerminalText that cover: stripping ANSI
color sequences, removing other control characters (e.g. null and DEL), handling
empty/undefined input, already-sanitized strings, and Windows CRLF line endings;
also include tests exercising the callers that sanitize account.email,
account.accountLabel, and account.accountId to ensure no regression.

---

Outside diff comments:
In `@lib/codex-manager.ts`:
- Around line 4120-4130: The menu uses flaggedStorage loaded by
loadFlaggedAccounts() but deleteAccountAtIndex may mutate/persist it and the
loop continues without reloading, causing stale flaggedCount shown to
promptLoginMode; fix by reloading or replacing flaggedStorage after any
deleteAccountAtIndex call (or change deleteAccountAtIndex to return the new
storage and assign it). Update the loop so that after calling
deleteAccountAtIndex you either call flaggedStorage = await
loadFlaggedAccounts() or assign the returned updated storage before continuing,
and ensure promptLoginMode/toExistingAccountInfo always receives the fresh
flaggedStorage for the flaggedCount.

In `@test/codex-manager-cli.test.ts`:
- Around line 51-54: The current tests mock promptLoginMode and
destructive-actions so they never exercise the real accept/cancel safety flow;
add focused Vitest tests that import the real prompt/menu modules (do not
vi.mock promptLoginMode or destructive-actions in these new tests) and call
runCodexMultiAuthCli to drive the confirm/cancel branches in lib/cli.ts and the
accept/cancel + fallback in lib/ui/auth-menu.ts, asserting both acceptance and
cancellation behavior. Use Vitest utilities (fake timers, controlled async
helpers) to keep tests deterministic and add targeted regression scenarios that
simulate concurrency/token-refresh races by orchestrating parallel promises
around runCodexMultiAuthCli and stub the token refresh entry points, and
simulate Windows filesystem behavior by stubbing fs/path functions (e.g.,
path.sep or fs.stat) to trigger the Windows branch; keep tests isolated and use
vitest mocks/stubs only for external side effects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0d39fe25-7aee-4de0-ae2d-a44182768739

📥 Commits

Reviewing files that changed from the base of the PR and between 6dc2521 and 7dcef36.

📒 Files selected for processing (10)
  • docs/troubleshooting.md
  • index.ts
  • lib/cli.ts
  • lib/codex-manager.ts
  • lib/destructive-actions.ts
  • lib/quota-cache.ts
  • lib/ui/auth-menu.ts
  • lib/ui/copy.ts
  • test/codex-manager-cli.test.ts
  • test/storage.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). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
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/ui/copy.ts
  • lib/quota-cache.ts
  • lib/destructive-actions.ts
  • lib/ui/auth-menu.ts
  • lib/cli.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/codex-manager-cli.test.ts
docs/**

⚙️ CodeRabbit configuration file

keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.

Files:

  • docs/troubleshooting.md
🪛 ast-grep (0.41.0)
lib/codex-manager.ts

[warning] 755-755: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp((?:^|\\|)\\s*${windowLabel}\\s+(\\d{1,3})%, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🪛 Biome (2.4.6)
lib/ui/auth-menu.ts

[error] 100-100: Unexpected control character in a regular expression.

(lint/suspicious/noControlCharactersInRegex)

🔇 Additional comments (3)
lib/codex-manager.ts (3)

4008-4011: lgtm on the signature change.

the optional flaggedStorage?: FlaggedAccountStorageV1 parameter is correctly typed and maintains backward compatibility. callers that don't need flagged account handling can omit it.


4195-4219: destructive action error scenarios lack test coverage; add tests for transient failures.

the cancel and accept flows are already tested at test/codex-manager-cli.test.ts:2161 (cancel), test/codex-manager-cli.test.ts:2186 (delete success), and test/codex-manager-cli.test.ts:2218 (reset success). however, there are no tests for error recovery when deleteSavedAccounts() or resetLocalState() throw.

add regression tests for:

  • transient EBUSY errors on windows during account/flagged account deletion
  • recovery from partial failures (e.g., accounts deleted but flagged accounts delete fails)
  • retries for rate-limited quota cache clears

the runActionPanel at lib/codex-manager.ts:4100+ captures errors but has no visible retry logic. compare this to the EBUSY/429 retry pattern already in the settings flow tests to ensure parity.


47-52: destructive action imports verified—proper windows fs handling and test coverage in place.

the new imports from lib/destructive-actions.ts (deleteSavedAccounts, resetLocalState, deleteAccountAtIndex) have solid backing: all delegate through storage helpers wrapped in withStorageLock mutex. lib/storage.ts:~1500 implements explicit EBUSY/EPERM retry with 5 attempts and exponential backoff (10ms * 2^N), directly addressing windows filesystem locking. test/storage.test.ts contains dedicated test suites ("saveAccounts EPERM/EBUSY retry logic") covering transient failures and all five retry exhaustion. integration layer tested in test/codex-manager-cli.test.ts confirms deleteSavedAccounts and resetLocalState flows. no token or email leaks in logging.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/cli.test.ts">

<violation number="1" location="test/cli.test.ts:615">
P2: These new fallback-cancel tests are outside the suite setup, so they can pass via `promptLoginMode`'s non-interactive early return instead of testing typed-confirm behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@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: 2

🤖 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/cli.ts`:
- Around line 278-283: The "reset-all" tty branch prints "Reset cancelled."
which is inconsistent with the fallback branch's message "Reset local state
cancelled."; update the console.log in the case "reset-all" (the block that runs
when promptResetTypedConfirm() returns false) to use the exact same cancellation
message ("Reset local state cancelled.") or extract a shared constant/string
used by the fallback to ensure both branches print the same text.

In `@test/cli.test.ts`:
- Around line 615-639: Move the two orphaned it(...) tests that start with
"cancels fallback delete-all..." and "cancels fallback reset..." into the
existing describe block for the CLI Module (or into the nested describe for
promptLoginMode); update them to rely on the shared beforeEach/afterEach setup
(remove the duplicated vi.mocked(createInterface) and mockRl setup if already
handled by the suite) and keep references to promptLoginMode, createInterface
and mockRl intact so they run with the same test fixtures and are grouped with
the other CLI tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 91f4c2ce-2e54-4d7a-986b-72f682fdc18c

📥 Commits

Reviewing files that changed from the base of the PR and between 7dcef36 and 617f984.

📒 Files selected for processing (3)
  • docs/troubleshooting.md
  • lib/cli.ts
  • test/cli.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). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
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/cli.test.ts
docs/**

⚙️ CodeRabbit configuration file

keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.

Files:

  • docs/troubleshooting.md
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/cli.ts
🔇 Additional comments (4)
lib/cli.ts (2)

149-171: typed confirmation guards now cover both destructive paths.

both promptDeleteAllTypedConfirm and promptResetTypedConfirm follow the same try/finally pattern ensuring readline cleanup. the fallback path at lib/cli.ts:205-222 and tty path at lib/cli.ts:272-283 both gate destructive actions behind these confirms.

minor observation: each confirm creates a new readline interface. if a user cancels and retries multiple times in a session, this creates/destroys interfaces repeatedly. not a bug, but reusing the existing rl from the caller might be cleaner. low priority.


216-222: fallback reset path correctly guarded.

the reset branch at lines 216-222 properly calls promptResetTypedConfirm() and only returns { mode: "reset" } on success. the continue on cancel loops back to mode selection. this addresses the past review comment about fallback bypassing safety flows.

docs/troubleshooting.md (1)

92-137: documentation correctly reflects cli behavior without unnecessary file removals.

the exact effects table and manual steps are accurate. flagged accounts don't use the wal/backup pattern that saved accounts do—saveFlaggedAccounts at lib/storage.ts:1324-1343 uses a simple atomic write (temp file → rename), not the writeFile wrapper with wal/backup that saveAccounts uses. so the docs are right to exclude openai-codex-flagged-accounts.json.wal and .bak* files from the reset local state commands. clearFlaggedAccounts at lib/storage.ts:1347-1357 only unlinks the main file, which matches the documented behavior.

test/cli.test.ts (1)

615-639: missing test coverage for reset-all path and successful reset confirmations.

the tests at lines 615-639 cover the fallback "reset" input cancel flow, which is good. however, the tty path where showAuthMenu returns { type: "reset-all" } (lib/cli.ts:79-84) has zero test coverage. additionally, even the fallback path lacks a test for successful reset confirmation (where user types "RESET" correctly), unlike the "fresh" path which has successful confirmation tests at lines 154-174.

add tests that:

  • mock showAuthMenu to return { type: "reset-all" } and verify the typed confirmation flow with valid "RESET" input
  • verify fallback path for successful reset confirmation with "r"/"reset" input followed by "RESET" confirmation

per coding guidelines, regression tests should cover windows readline edge cases (EBUSY on file conflicts, readline interface lifecycle on windows during rapid create/close cycles).

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/quota-cache.ts">

<violation number="1" location="lib/quota-cache.ts:275">
P2: `clearQuotaCache()` still uses a module-initialized `QUOTA_CACHE_PATH`, so runtime `CODEX_MULTI_AUTH_DIR` changes are ignored and the wrong cache file can be targeted.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/quota-cache.test.ts">

<violation number="1" location="test/quota-cache.test.ts:103">
P2: Use retry-based cleanup instead of single-shot `fs.rm` to avoid Windows EBUSY/EPERM test flakes.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@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: 9

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)

4093-4117: ⚠️ Potential issue | 🔴 Critical

wait for the background quota refresh before running destructive or manage actions.

pendingMenuQuotaRefresh can still be inside refreshQuotaCacheForMenu() and call saveQuotaCache() from lib/codex-manager.ts:700-702 after resetLocalState() clears the cache in lib/destructive-actions.ts:146-155. if the user picks reset while the menu auto-fetch is in flight, quota-cache.json can be recreated after the flow reports success. the same race can also repopulate stale entries after delete/manage actions. await or cancel the pending refresh before these paths, and add a vitest regression in test/codex-manager-cli.test.ts that reproduces it. 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: 4195-4251

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

In `@lib/codex-manager.ts` around lines 4093 - 4117, pendingMenuQuotaRefresh can
race with destructive/manage flows and recreate quota-cache after
resetLocalState; fix by ensuring destructive/manage entry points await or cancel
the in-flight pendingMenuQuotaRefresh Promise returned by
refreshQuotaCacheForMenu (or expose an abort mechanism from
refreshQuotaCacheForMenu) before calling saveQuotaCache or resetLocalState;
update the code paths that perform reset/manage/delete to check
pendingMenuQuotaRefresh (and await .finally or call an abort API) so cache
writes cannot win the race, add a vitest regression in
test/codex-manager-cli.test.ts that triggers reset/manage while
refreshQuotaCacheForMenu is in flight to reproduce the issue, and when
introducing the queue/abort handling ensure retries handle EBUSY/429 and that
logging around these changes avoids leaking tokens/emails.
♻️ Duplicate comments (2)
lib/destructive-actions.ts (1)

103-105: ⚠️ Potential issue | 🔴 Critical

rebase active indices before clamping after a delete.

after splice(), clampActiveIndices() only bounds values. if options.index is below activeIndex or any activeIndexByFamily entry, those pointers stay one too high and silently retarget the next account instead of the previously selected one. decrement indices greater than the removed slot before lib/destructive-actions.ts:104, and add a vitest for that path because test/destructive-actions.test.ts does not cover it today. 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/destructive-actions.ts` around lines 103 - 105, After removing an account
with nextStorage.accounts.splice(options.index, 1) in
lib/destructive-actions.ts, adjust any stored pointers that point past the
removed slot (e.g., activeIndex and entries in activeIndexByFamily) by
decrementing values greater than options.index before calling
clampActiveIndices(nextStorage) and saveAccounts(nextStorage); update the code
paths that mutate storage (the splice block around nextStorage/accounts) to
perform this rebase, add a vitest in test/destructive-actions.test.ts that
deletes an earlier index and asserts activeIndex and activeIndexByFamily were
moved to the previous account, and ensure your changes follow the repo
guidelines by updating any affected queues to handle EBUSY/429 and avoiding logs
that leak tokens or emails.
lib/codex-manager.ts (1)

4028-4033: ⚠️ Potential issue | 🟠 Major

mask account labels in deletion logs.

formatAccountLabel() is written straight to stdout here. test/codex-manager-cli.test.ts:63-65 models that label with the raw email address, so this delete path prints pii to the console. use a log-safe or masked label instead. 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/codex-manager.ts` around lines 4028 - 4033, Log currently prints raw
account labels using formatAccountLabel(deleted.removedAccount,...), leaking
PII; replace that call with a masked/log-safe label generator (e.g.,
maskEmailLabel or formatAccountLabelSafe) that redacts identifiable parts of
deleted.removedAccount while preserving the idx context, then use the masked
label in the console output instead of formatAccountLabel. Update the affected
test (test/codex-manager-cli.test.ts lines ~63-65) to assert the masked form is
logged (not the raw email), and run the vitest suite; also scan
lib/codex-manager.ts for any other console/log calls that use full emails or
tokens and switch them to the same masking helper.
🤖 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/destructive-actions.ts`:
- Around line 114-118: When saveFlaggedAccounts(updatedFlagged) fails, we must
not lose that original error if the rollback saveAccounts(previousStorage) also
throws; wrap the rollback call in its own try/catch inside the catch block for
saveFlaggedAccounts and if the rollback throws create and throw an
AggregateError that includes both the original error and the rollback error
(preserving both). Update the catch around saveFlaggedAccounts in the function
handling updatedFlagged/previousStorage to catch rollback errors separately and
throw new AggregateError([originalError, rollbackError], 'saving flagged
accounts failed and rollback failed').
- Around line 135-139: The return object from deleteSavedAccounts incorrectly
claims flaggedCleared and quotaCacheCleared are true even though the function
only calls clearAccounts(); update deleteSavedAccounts so it only sets
flaggedCleared and quotaCacheCleared to true when those specific clear
operations actually occur: either call the appropriate helpers (e.g.,
clearFlagged, clearQuotaCache) and await their results and use those booleans,
or set those fields to false if you didn't perform those clears; ensure
clearAccounts() result is used for accountsCleared and do not assert
cleared=true for flaggedCleared/quotaCacheCleared unless verified.

In `@lib/quota-cache.ts`:
- Around line 272-295: The module currently uses the module-scoped
QUOTA_CACHE_PATH which does not update when CODEX_MULTI_AUTH_DIR changes; update
clearQuotaCache() (and similarly saveQuotaCache() and any other helpers) to
resolve the path lazily by calling getQuotaCachePath() at runtime instead of
referencing QUOTA_CACHE_PATH, then use that returned path for
fs.unlink/fs.writeFile/etc., preserving the existing retry/backoff and ENOENT
handling; remove reliance on the stale QUOTA_CACHE_PATH variable so tests that
change CODEX_MULTI_AUTH_DIR target the correct file.

In `@lib/storage.ts`:
- Around line 1307-1339: clearAccounts currently unlinks only the primary path,
wal and fixed backup slots; update clearAccounts to enumerate and delete every
recovery surface that loadAccountsInternal can read (use
getAccountsBackupRecoveryCandidates result plus legacy project/worktree store
paths referenced in loadAccountsInternal and the legacy path helpers), and
perform each delete via the same backoff retry helper used elsewhere for
filesystem deletes so transient EPERM/EBUSY on Windows is retried; keep
per-artifact logging and preserve the hadError semantics, and update/add vitest
cases that assert “delete saved accounts” stays empty when only legacy or extra
backup artifacts exist.
- Around line 1534-1552: clearFlaggedAccounts currently only removes the new
flagged-store and can allow the legacy openai-codex-blocked-accounts.json to be
migrated back by loadFlaggedAccounts; update clearFlaggedAccounts to unlink both
getFlaggedAccountsPath() and the legacy filename
("openai-codex-blocked-accounts.json") using the same windows-safe retry/backoff
logic used elsewhere for unlink (handle EBUSY, EACCES, and transient 429-like
conditions), ensure operations are serialized via withStorageLock, and return
true only when both removals either succeed or are ENOENT; add a vitest that
simulates the legacy file present after clear (including a transient EBUSY on
unlink to validate retry/backoff) to prevent legacy-file resurrection and
reference the adjusted functions clearFlaggedAccounts and loadFlaggedAccounts in
the test description.
- Around line 1227-1249: saveAccountsUnlocked currently performs its own
temp→final rename loop that only retries EPERM/EBUSY, missing EAGAIN handling;
route the rename in saveAccountsUnlocked to use the existing renameFileWithRetry
helper (or at minimum add EAGAIN to the retry set) so transient EAGAIN is
handled the same as EPERM/EBUSY, and update/add a vitest that simulates one
transient EAGAIN on fs.rename before a successful rename to assert the save
succeeds and cleanup (wal unlink) still runs; reference renameFileWithRetry and
saveAccountsUnlocked when implementing and updating tests.

In `@test/destructive-actions.test.ts`:
- Around line 31-167: Add deterministic vitest coverage for resetLocalState by
exercising the exported function resetLocalState (and the existing
deleteSavedAccounts and deleteAccountAtIndex flows) to assert the new three-flag
result shape (accountsCleared, flaggedCleared, quotaCacheCleared) and that
clearCodexCliStateCacheMock is invoked when appropriate; create tests that
simulate successful clears, partial failures (e.g., make
clearFlaggedAccountsMock or clearQuotaCacheMock reject once) and ensure
deleteSavedAccounts returns correct flags without pretending others were
cleared, and simulate rollback scenarios for deleteAccountAtIndex where
saveFlaggedAccountsMock rejects then ensure resetLocalState's cache-clear side
effect (clearCodexCliStateCacheMock) is called or not as expected; keep all
mocks deterministic via vi.resetModules/clearAllMocks, avoid using real secrets
or non-deterministic timers, and assert storage mutation (or lack thereof) and
error aggregation behavior (AggregateError) for concurrent/rollback failure
cases.

In `@test/quota-cache.test.ts`:
- Around line 82-105: Add deterministic tests for the windows retry branches in
clearQuotaCache by mocking the filesystem unlink/rm calls to reproduce transient
EBUSY/EPERM and exhausted-retry behavior: import clearQuotaCache,
saveQuotaCache, and getQuotaCachePath from "../lib/quota-cache.js" as in the
existing test, then write one test that stubs the fs.unlink/fs.rm (same function
used by clearQuotaCache) to throw an { code: 'EBUSY' } or { code: 'EPERM' } on
the first N-1 calls and succeed on the Nth call, asserting clearQuotaCache
eventually removes the file and resolves; write another test that stubs the same
function to always throw EBUSY/EPERM (or to exceed the retry count) and assert
clearQuotaCache rejects/throws after retries are exhausted; ensure you use
vitest spies/mocks (vi.spyOn or vi.fn) and restore mocks between tests and still
call saveQuotaCache/getQuotaCachePath to create the target file so tests remain
deterministic and consistent with the existing test patterns.

---

Outside diff comments:
In `@lib/codex-manager.ts`:
- Around line 4093-4117: pendingMenuQuotaRefresh can race with
destructive/manage flows and recreate quota-cache after resetLocalState; fix by
ensuring destructive/manage entry points await or cancel the in-flight
pendingMenuQuotaRefresh Promise returned by refreshQuotaCacheForMenu (or expose
an abort mechanism from refreshQuotaCacheForMenu) before calling saveQuotaCache
or resetLocalState; update the code paths that perform reset/manage/delete to
check pendingMenuQuotaRefresh (and await .finally or call an abort API) so cache
writes cannot win the race, add a vitest regression in
test/codex-manager-cli.test.ts that triggers reset/manage while
refreshQuotaCacheForMenu is in flight to reproduce the issue, and when
introducing the queue/abort handling ensure retries handle EBUSY/429 and that
logging around these changes avoids leaking tokens/emails.

---

Duplicate comments:
In `@lib/codex-manager.ts`:
- Around line 4028-4033: Log currently prints raw account labels using
formatAccountLabel(deleted.removedAccount,...), leaking PII; replace that call
with a masked/log-safe label generator (e.g., maskEmailLabel or
formatAccountLabelSafe) that redacts identifiable parts of
deleted.removedAccount while preserving the idx context, then use the masked
label in the console output instead of formatAccountLabel. Update the affected
test (test/codex-manager-cli.test.ts lines ~63-65) to assert the masked form is
logged (not the raw email), and run the vitest suite; also scan
lib/codex-manager.ts for any other console/log calls that use full emails or
tokens and switch them to the same masking helper.

In `@lib/destructive-actions.ts`:
- Around line 103-105: After removing an account with
nextStorage.accounts.splice(options.index, 1) in lib/destructive-actions.ts,
adjust any stored pointers that point past the removed slot (e.g., activeIndex
and entries in activeIndexByFamily) by decrementing values greater than
options.index before calling clampActiveIndices(nextStorage) and
saveAccounts(nextStorage); update the code paths that mutate storage (the splice
block around nextStorage/accounts) to perform this rebase, add a vitest in
test/destructive-actions.test.ts that deletes an earlier index and asserts
activeIndex and activeIndexByFamily were moved to the previous account, and
ensure your changes follow the repo guidelines by updating any affected queues
to handle EBUSY/429 and avoiding logs that leak tokens or emails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 04f1d77f-c129-4fb0-9317-50bef940da6d

📥 Commits

Reviewing files that changed from the base of the PR and between 7194c46 and 78bdb5b.

📒 Files selected for processing (9)
  • index.ts
  • lib/codex-manager.ts
  • lib/destructive-actions.ts
  • lib/quota-cache.ts
  • lib/storage.ts
  • test/codex-manager-cli.test.ts
  • test/destructive-actions.test.ts
  • test/quota-cache.test.ts
  • test/storage.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). (2)
  • GitHub Check: cubic · AI code reviewer
  • 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/quota-cache.test.ts
  • test/destructive-actions.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/quota-cache.ts
  • lib/destructive-actions.ts
  • lib/storage.ts
  • lib/codex-manager.ts
🪛 ast-grep (0.41.1)
lib/codex-manager.ts

[warning] 755-755: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp((?:^|\\|)\\s*${windowLabel}\\s+(\\d{1,3})%, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

# Conflicts:
#	index.ts
#	lib/codex-manager.ts
#	lib/storage.ts
#	test/codex-manager-cli.test.ts
#	test/storage-flagged.test.ts
#	test/storage.test.ts
Copy link
Contributor

@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: 9

Caution

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

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

3743-3761: ⚠️ Potential issue | 🔴 Critical

do not pass a stale flagged snapshot into account deletion.

lib/codex-manager.ts:3743-3761 and lib/codex-manager.ts:3955-3960 thread the flaggedStorage snapshot loaded before the menu render at lib/codex-manager.ts:3848 into deleteAccountAtIndex(). if another process updates flagged storage while the menu is open, this path writes the stale snapshot back and drops the newer flagged entries. let deleteAccountAtIndex() load flagged storage at delete time, or reload it immediately before the call, and add a vitest for that race.

proposed fix
-async function handleManageAction(
-	storage: AccountStorageV3,
-	menuResult: Awaited<ReturnType<typeof promptLoginMode>>,
-	flaggedStorage?: FlaggedAccountStorageV1,
-): Promise<void> {
+async function handleManageAction(
+	storage: AccountStorageV3,
+	menuResult: Awaited<ReturnType<typeof promptLoginMode>>,
+): Promise<void> {
@@
-			const deleted = await deleteAccountAtIndex({
-				storage,
-				index: idx,
-				flaggedStorage,
-			});
+			const deleted = await deleteAccountAtIndex({
+				storage,
+				index: idx,
+			});

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: 3953-3960

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

In `@lib/codex-manager.ts` around lines 3743 - 3761, The delete path is passing a
stale flaggedStorage snapshot into deleteAccountAtIndex from handleManageAction
(flaggedStorage loaded before menu render), which can overwrite newer changes;
change the logic so deleteAccountAtIndex always reads current flagged storage
itself or reload flaggedStorage immediately before calling deleteAccountAtIndex
(remove the flaggedStorage argument from the stale call site in
handleManageAction and/or update deleteAccountAtIndex to load fresh flagged data
internally), and add a vitest that simulates concurrent flaggedStorage update
while the menu is open to ensure the newer entries are not lost; reference
functions/variables: handleManageAction, deleteAccountAtIndex, flaggedStorage,
and the menu deleteAccountIndex code path.
lib/storage.ts (1)

2004-2043: ⚠️ Potential issue | 🟡 Minor

legacy paths aren't explicitly cleared, though reset marker blocks resurrection.

clearAccounts clears primary, wal, and backup candidates but not currentLegacyProjectStoragePath or currentLegacyWorktreeStoragePath. if legacy files exist, migration deletes them (lib/storage.ts:922), but only during load. clearAccounts itself doesn't clean them up.

the reset marker at lib/storage.ts:1652 prevents loading stale data, so physical legacy files pose no practical risk—but test coverage is missing. add a regression test in test/storage.test.ts that creates legacy files, calls clearAccounts, and verifies they're handled correctly.

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

In `@lib/storage.ts` around lines 2004 - 2043, clearAccounts currently removes
primary, wal, and backup candidates but omits the legacy storage files
referenced by currentLegacyProjectStoragePath and
currentLegacyWorktreeStoragePath; update clearAccounts to also compute those two
legacy paths for the same storage root (alongside getStoragePath(),
getAccountsWalPath(), and getAccountsBackupRecoveryCandidatesWithDiscovery())
and call unlinkWithRetry on them with the same per-artifact error handling
(preserve hadError logic and logging). Additionally add a regression test in
test/storage.test.ts that creates files at currentLegacyProjectStoragePath and
currentLegacyWorktreeStoragePath, invokes clearAccounts(), and asserts the
legacy files no longer exist (or that clearAccounts returns true and no legacy
artifacts remain).
🤖 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 3108-3112: The call to deleteAccountAtIndex returns an updated
storage object in deleted.storage but the caller continues using the stale
workingStorage; after the deleteAccountAtIndex call (where you assign const
deleted = await deleteAccountAtIndex(...)), rebind the in-memory state by
setting workingStorage = deleted.storage so subsequent iterations use the
updated storage; apply the same fix at the other call site where
deleteAccountAtIndex is used (the call in codex-manager around the other loop)
and add a regression test that performs multiple sequential deletes in a loop
and asserts in-memory workingStorage matches the returned storage/disk state
after each delete.
- Around line 3163-3177: The reset branch in index.ts (the block that checks
menuResult.mode === "reset" and calls resetLocalState(),
invalidateAccountManagerCache(), and logs
DESTRUCTIVE_ACTION_COPY.resetLocalState) lacks the in-flight concurrency guard
used in lib/codex-manager.ts and has no tests; add the same "in-flight" guard
semantics used around the reset flow in lib/codex-manager.ts to prevent double
invocation, and update the index.ts reset path to return/skip when a reset is
already running. Then add regression tests in test/codex-manager-cli.test.ts
that cover: (1) successful reset logging (all flags true ->
DESTRUCTIVE_ACTION_COPY.resetLocalState.completed), (2) warning logging when
some flags are false, and (3) double-invocation protection (attempting a second
reset while the first is in-flight should be prevented/short-circuited). Ensure
the tests exercise resetLocalState(), invalidateAccountManagerCache(), and the
printed messages.

In `@lib/cli.ts`:
- Around line 210-214: The cancellation message for the delete-all flow is
inconsistent: the branch that uses promptDeleteAllTypedConfirm() logs "Delete
saved accounts cancelled." but the TTY branch logs "Delete all cancelled.";
update the TTY branch to use the same standardized wording ("Delete saved
accounts cancelled.") so both branches (the promptDeleteAllTypedConfirm() branch
and the TTY handling around the return { mode: "fresh", deleteAll: true } / the
other cancellation at lines ~273-277) report the identical cancellation message.

In `@lib/storage.ts`:
- Around line 182-200: The unlinkWithRetry function only retries on EPERM and
EBUSY; update its retry condition to also include EAGAIN to match
renameFileWithRetry, i.e., in lib/storage.ts add "EAGAIN" alongside "EPERM" and
"EBUSY" in the conditional that waits-and-retries, and ensure the backoff logic
remains the same; then add a regression test in test/storage.test.ts that
simulates a transient EAGAIN error on fs.unlink (flaky first few attempts, then
succeed) to verify unlinkWithRetry retries and eventually succeeds (mirror the
approach used for the rename EAGAIN test).

In `@lib/ui/auth-menu.ts`:
- Around line 683-690: The subtitle is built from caller-provided statusMessage
and normalizedSearch without sanitization, allowing control bytes/escape
sequences to corrupt the UI; update resolveStatusMessage and any place that
returns/uses normalizedSearch (e.g., promptSearchQuery and the code around
normalizedSearch at lib/ui/auth-menu.ts) to pass the raw string through
sanitizeTerminalText() before trimming/returning, ensuring both statusMessage()
results and normalizedSearch are sanitized (use the existing
sanitizeTerminalText function name as the sanitizer) so the subtitle only
contains cleaned text.

In `@test/auth-menu-hotkeys.test.ts`:
- Line 261: The test assertion using the regex /[\u0000\u0007\u007f]/ is
intentionally checking control-character removal but trips Biome's
noControlCharactersInRegex rule; fix it by adding a suppression comment
immediately above the assertion line containing the regex (the expect on the
variable strippedLabels) using: // biome-ignore
lint/suspicious/noControlCharactersInRegex: intentional test assertion; this
keeps the intent clear and preserves the original Unicode escapes used in the
test.

In `@test/codex-manager-cli.test.ts`:
- Around line 156-175: The test mock for DESTRUCTIVE_ACTION_COPY is missing the
typedConfirm and confirm string fields present in the real export; update the
mock inside the vi.mock call to include typedConfirm and confirm keys (matching
the real structure used by code paths that render typed confirmation prompts)
alongside the existing deleteSavedAccounts and resetLocalState entries so that
DESTRUCTIVE_ACTION_COPY.typedConfirm and DESTRUCTIVE_ACTION_COPY.confirm return
the expected prompt strings when referenced by the code under test.

In `@test/destructive-actions.test.ts`:
- Around line 31-248: Add a deterministic vitest that hits the codex-manager
serialization guard by invoking the CLI path that routes to the destructive
actions (use the codex-manager's in-flight guard / "in-flight" flag around
performing destructive actions) instead of importing lib/destructive-actions
directly; mock the underlying clear* helpers (clearAccountsMock,
clearFlaggedAccountsMock, clearQuotaCacheMock) with controlled deferred promises
so the first call remains pending, then call the same CLI entrypoint a second
time and assert it immediately rejects (or returns the guard error) while the
first is in-flight, finally resolve/reject the deferred promise to simulate
completion/exception and assert the in-flight flag is cleared so subsequent
calls succeed; reference the CLI entry functions you call (the manager's CLI
handlers that call deleteSavedAccounts and resetLocalState) and the mocked
helpers (clearAccountsMock, clearFlaggedAccountsMock, clearQuotaCacheMock) to
locate where to hook the deferred promises.

In `@test/storage.test.ts`:
- Around line 3293-3356: Add two deterministic vitest cases for clearQuotaCache:
one that simulates a transient Windows EBUSY (mock fs.unlink to throw a
NodeJS.ErrnoException with code "EBUSY" once then succeed) and assert
clearQuotaCache resolves true and unlink called twice; and one that simulates
repeated transient failures until retries are exhausted (mock fs.unlink to
always throw EPERM/EBUSY) and assert clearQuotaCache resolves false and unlink
was called the expected retry count; target the existing test suite around
clearQuotaCache/getQuotaCachePath and use vi.spyOn(fs, "unlink") with
mockImplementation and proper mockRestore to keep tests deterministic.

---

Outside diff comments:
In `@lib/codex-manager.ts`:
- Around line 3743-3761: The delete path is passing a stale flaggedStorage
snapshot into deleteAccountAtIndex from handleManageAction (flaggedStorage
loaded before menu render), which can overwrite newer changes; change the logic
so deleteAccountAtIndex always reads current flagged storage itself or reload
flaggedStorage immediately before calling deleteAccountAtIndex (remove the
flaggedStorage argument from the stale call site in handleManageAction and/or
update deleteAccountAtIndex to load fresh flagged data internally), and add a
vitest that simulates concurrent flaggedStorage update while the menu is open to
ensure the newer entries are not lost; reference functions/variables:
handleManageAction, deleteAccountAtIndex, flaggedStorage, and the menu
deleteAccountIndex code path.

In `@lib/storage.ts`:
- Around line 2004-2043: clearAccounts currently removes primary, wal, and
backup candidates but omits the legacy storage files referenced by
currentLegacyProjectStoragePath and currentLegacyWorktreeStoragePath; update
clearAccounts to also compute those two legacy paths for the same storage root
(alongside getStoragePath(), getAccountsWalPath(), and
getAccountsBackupRecoveryCandidatesWithDiscovery()) and call unlinkWithRetry on
them with the same per-artifact error handling (preserve hadError logic and
logging). Additionally add a regression test in test/storage.test.ts that
creates files at currentLegacyProjectStoragePath and
currentLegacyWorktreeStoragePath, invokes clearAccounts(), and asserts the
legacy files no longer exist (or that clearAccounts returns true and no legacy
artifacts remain).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 614264cc-2d12-4a08-b225-4bea65461b59

📥 Commits

Reviewing files that changed from the base of the PR and between 78bdb5b and b0c83b9.

📒 Files selected for processing (17)
  • index.ts
  • lib/cli.ts
  • lib/codex-manager.ts
  • lib/destructive-actions.ts
  • lib/quota-cache.ts
  • lib/storage.ts
  • lib/ui/auth-menu.ts
  • lib/ui/copy.ts
  • test/auth-menu-hotkeys.test.ts
  • test/cli-auth-menu.test.ts
  • test/cli.test.ts
  • test/codex-manager-cli.test.ts
  • test/destructive-actions.test.ts
  • test/quota-cache.test.ts
  • test/release-main-prs-regression.test.ts
  • test/storage-flagged.test.ts
  • test/storage.test.ts
📜 Review details
🧰 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/auth-menu-hotkeys.test.ts
  • test/cli-auth-menu.test.ts
  • test/release-main-prs-regression.test.ts
  • test/quota-cache.test.ts
  • test/storage.test.ts
  • test/codex-manager-cli.test.ts
  • test/cli.test.ts
  • test/destructive-actions.test.ts
  • test/storage-flagged.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/cli.ts
  • lib/quota-cache.ts
  • lib/ui/copy.ts
  • lib/storage.ts
  • lib/destructive-actions.ts
  • lib/ui/auth-menu.ts
  • lib/codex-manager.ts
🪛 Biome (2.4.6)
test/auth-menu-hotkeys.test.ts

[error] 261-261: Unexpected control character in a regular expression.

(lint/suspicious/noControlCharactersInRegex)


[error] 261-261: Unexpected control character in a regular expression.

(lint/suspicious/noControlCharactersInRegex)

🔇 Additional comments (26)
test/release-main-prs-regression.test.ts (1)

202-202: lgtm — test correctly expects false on eperm.

the updated assertion aligns with the new clearFlaggedAccounts() return semantics where permission errors resolve to false instead of rejecting. the subsequent assertions at lines 207-210 confirm the file persists and sync doesn't pick up stale data.

lib/ui/copy.ts (1)

18-19: ui copy updated for reset flow.

new resetLocalState label and updated removeAllAccounts wording align with the destructive-actions module. the fallback prompt at line 134 correctly adds the (r) reset option.

lib/quota-cache.ts (1)

277-301: clearQuotaCache looks solid, but the trailing return is unreachable.

the 5-retry loop with exponential backoff matches the pattern in saveQuotaCache at lib/quota-cache.ts:246-254. the lazy path resolution via getQuotaCachePath() fixes the stale-path issue from earlier reviews.

one nit: line 300 return false is unreachable — the loop always returns inside (success, ENOENT, or exhausted retries on line 294). harmless but dead code.

test coverage confirmed in test/quota-cache.test.ts:108-184.

test/quota-cache.test.ts (3)

108-140: good regression coverage for transient unlink failures.

the parameterized test properly exercises both EBUSY and EPERM retry paths. the mock at line 118-130 fails the first 2 attempts then delegates to the real unlink, verifying the retry loop eventually succeeds. assertion at line 135 confirms exactly 3 attempts.


142-184: exhausted-retry test correctly validates warning and return value.

the mock at line 158-166 forces all 5 attempts to fail. assertions at lines 169-176 verify the function returns false, attempts = 5, file still exists (because unlink was mocked), and the warning includes the error message.

one note: line 171 asserts the file still exists via fs.access, which is correct since the mock prevents actual deletion. the real behavior would be file-still-exists on exhausted retries.


83-106: lazy path resolution test addresses prior review feedback.

this validates that getQuotaCachePath() reflects env changes at runtime, fixing the stale QUOTA_CACHE_PATH module-scope issue flagged in earlier reviews. the test creates a second temp dir at line 87-89, verifies the path changes at line 95-96, and confirms save/clear target the new location.

test/cli.test.ts (2)

176-217: good coverage for fallback reset confirmation and cancellation.

tests at lines 176-185 verify the happy path where typing "RESET" confirms the reset action. lines 203-217 verify cancellation when confirmation doesn't match, including the correct cancellation message "Reset local state cancelled.". these align with the fallback flow in lib/cli.ts:216-222.


219-321: tty reset-all flow tested with proper mock setup.

the tests correctly mock isTTY and showAuthMenu to exercise the tty path. line 246 mocks the typed confirm, line 299 tests the cancellation path. both restore stdin/stdout iTTY in the finally block.

test/auth-menu-hotkeys.test.ts (1)

201-219: confirm prompt removal validated for delete-all and reset-all.

these tests verify the pr objective: typed confirmations are now the single safety gate in the cli layer, so the tui's showAuthMenu returns the action directly without calling confirm(). line 208 and 218 assert confirmMock is not called.

test/cli-auth-menu.test.ts (1)

297-323: reset-all tty flow tested for both confirm and cancel paths.

lines 297-306 verify typing "RESET" confirms the action and closes readline. lines 308-323 verify mismatched confirmation logs "\nReset local state cancelled.\n" and falls back to cancel. these align with lib/cli.ts:278-283.

lib/cli.ts (3)

161-171: promptResetTypedConfirm follows the established pattern.

mirrors promptDeleteAllTypedConfirm at lines 149-158. uses DESTRUCTIVE_ACTION_COPY.resetLocalState.typedConfirm for the prompt, checks for exact "RESET" match, and properly closes the readline interface in finally.


216-222: fallback reset path now requires typed confirmation.

this addresses the past review comment about the fallback bypassing destructive safety. line 217 calls promptResetTypedConfirm(), and line 218 uses consistent cancellation message "Reset local state cancelled.".

test coverage in test/cli.test.ts:203-217.


278-283: tty reset-all path uses consistent cancellation message.

line 280 now logs "Reset local state cancelled." which matches the fallback at line 218, addressing the past review comment about message inconsistency.

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

3209-3240: test correctly validates cli call site.

the mock-based approach is appropriate for verifying the cli layer passes the correct index to deleteAccountAtIndex. the actual index rebase regression test should live in test/destructive-actions.test.ts where the real module logic is exercised.


3446-3517: good concurrency regression test.

the deferred-promise approach makes the test deterministic while exercising the destructiveActionInFlight guard. this validates that concurrent destructive actions are properly serialized at the cli layer.


3362-3444: solid regression test for quota refresh ordering.

validates that resetLocalState waits for in-flight quota refresh to complete before clearing the cache. the invocation order assertion at lines 3436-3439 is a good check for the race condition fix.

lib/storage.ts (3)

1857-1869: good refactor using shared rename helper.

this addresses the past review comment about the main save path missing EAGAIN retries. using renameFileWithRetry centralizes the retry logic and ensures consistent handling of windows filesystem transients.


2267-2317: reset marker approach correctly prevents legacy flagged resurrection.

even though the legacy flagged path (openai-codex-blocked-accounts.json) isn't explicitly deleted, loadFlaggedAccounts at lib/storage.ts:2167 returns empty when the reset marker exists. the conditional marker cleanup at lines 2301-2315 ensures the marker persists on partial failure, maintaining protection.


1947-1948: correct fix for transaction state consistency.

exportAccounts at lib/storage.ts:2338 checks transactionState?.active to determine if it's running inside a transaction. adding active: true here ensures withAccountAndFlaggedStorageTransaction behaves consistently with withAccountStorageTransaction.

lib/destructive-actions.ts (5)

107-169: deleteAccountAtIndex handles rollback correctly.

the shallow copy at line 117 is sufficient since we're only removing an account from the array, not mutating individual account objects. the AggregateError at lines 148-157 preserves both failures when rollback also fails, addressing the past review comment.

note: saveAccounts internally calls withStorageLock (lib/storage.ts:1994-1998), so the direct calls here are safe from concurrent storage mutations.


175-181: correct result contract.

returns flaggedCleared: false and quotaCacheCleared: false since those operations aren't performed. this addresses the past review comment about lying result contracts.


15-36: clear user-facing copy.

the typed confirmation prompts clearly enumerate what will be deleted vs kept, reducing accidental data loss. the as const assertion enables proper type inference for the string literals.


187-197: [rewritten review comment]
[classification tag]


71-86: index rebase logic is correct and tested.

rebaseActiveIndicesAfterDelete at lib/destructive-actions.ts:71-86 decrements indices greater than the removed position before clampActiveIndices bounds them. this prevents silent mis-targeting after splice. test coverage at test/destructive-actions.test.ts:92-138 verifies the exact scenario: deleting index 0 with activeIndex: 1 and activeIndexByFamily: { codex: 2, "gpt-5.x": 1 } correctly adjusts to activeIndex: 0 and { codex: 1, "gpt-5.x": 0 }. rollback with AggregateError at lib/destructive-actions.ts:140-160 handles double-failure correctly.

index.ts (2)

104-109: good consolidation of destructive action imports.

this keeps destructive flows and copy centralized, which reduces drift between menu paths. ref: lib/destructive-actions.ts:1-15.


3150-3157: good partial-failure messaging for delete-all.

line 3153 reports warnings when cleanup is incomplete instead of claiming full success. this is the right behavior for windows file-lock edge cases. ref: lib/destructive-actions.ts:1-15.

@ndycode
Copy link
Owner Author

ndycode commented Mar 13, 2026

@greptile @cubic review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 13, 2026

@greptile @cubic review

@ndycode I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 18 files

@ndycode ndycode changed the title fix(auth): define reset and delete safety flows OK fix(auth): define reset and delete safety flows Mar 13, 2026
@ndycode ndycode added the enhancement New feature or request label Mar 13, 2026
@ndycode ndycode changed the title OK fix(auth): define reset and delete safety flows fix(auth): define reset and delete safety flows Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant