Skip to content

add onboarding restore for latest saved backup#137

Merged
ndycode merged 25 commits intomainfrom
feat/load-last-backup-onboarding
Mar 20, 2026
Merged

add onboarding restore for latest saved backup#137
ndycode merged 25 commits intomainfrom
feat/load-last-backup-onboarding

Conversation

@ndycode
Copy link
Copy Markdown
Owner

@ndycode ndycode commented Mar 20, 2026

Summary

  • add empty-pool onboarding restore for valid named backups
  • harden restore state against backup scan races and first-add loop drift
  • add regression coverage for refreshed backup picks, UNC containment, and restore disappearance paths

Validation

  • npm test -- --run test/codex-manager-cli.test.ts test/storage-last-backup.test.ts test/documentation.test.ts
  • npm run typecheck
  • npm run lint
  • npm run build

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 adds an onboarding backup restore path to codex auth login: when the active account pool is empty and at least one valid named backup exists, the sign-in menu gains a "restore saved backup" option that lets the user load the latest backup automatically or pick from a sorted list. the feature hooks into runAuthLogin's existing inner loop, adds getNamedBackups / restoreAccountsFromBackup to lib/storage.ts with realpath-based containment checks and enoent guards, and extends persistAndSyncSelectedAccount with a preserveActiveIndexByFamily flag to keep per-family routing from the restored data when internally consistent.

  • lib/storage.tscollectNamedBackups uses stat-before-load + stat-after-load race detection; restoreAccountsFromBackup guards realpath(backupRoot) and realpath(path) separately and wraps loadAccountsFromPath in an enoent handler, preventing raw node errors from reaching the user on windows when antivirus locks or deletes a file between scan and restore.
  • lib/codex-manager.tspromptOAuthSignInMode now accepts a NamedBackupSummary | null and a discovery warning; the restore-backup branch in the inner loop always calls confirm() before writing, runs restoreAccountsFromBackup with persist: false and commits a single saveAccounts write via persistAndSyncSelectedAccount, and wraps runActionPanel in a try/catch that keeps the session alive on windows ebusy/eperm failures.
  • one minor issue: onboardingBackupDiscoveryWarning is not reset alongside namedBackups = [] after a successful oauth login, leaving a stale "backup discovery failed" subtitle visible in the add-more-accounts flow for users who hit a prior eperm on the backup dir. fix is a one-liner: onboardingBackupDiscoveryWarning = null at the same site.
  • "restore" is consistently added to lastSwitchReason across lib/schemas.ts, lib/accounts.ts, lib/storage/migrations.ts, and the normalizeFlaggedStorage guard.
  • test coverage is thorough: 13 new cli integration tests and 16 storage unit tests, including windows unc containment, symlink escape, stat/realpath race simulation, stale index clamping, and the full confirm/back/error-recovery matrix.

Confidence Score: 4/5

  • safe to merge — one minor ux issue with a stale warning string in an edge case, no correctness or token-safety regressions found.
  • all prior-thread concerns (realpath enoent guards, loadAccountsFromPath enoent guard, containment check, confirm gate, runActionPanel try/catch, single saveAccounts write) are addressed. the only new finding is onboardingBackupDiscoveryWarning not being cleared when namedBackups is zeroed after account addition — cosmetic/ux only, not a data or token safety risk. test coverage is comprehensive including windows-specific paths.
  • lib/codex-manager.ts — the namedBackups = [] / onboardingBackupDiscoveryWarning reset site around line 4508.

Important Files Changed

Filename Overview
lib/codex-manager.ts major additions: promptOAuthSignInMode/promptBackupRestoreMode/promptManualBackupSelection, inline restore action in runAuthLogin. one minor issue: onboardingBackupDiscoveryWarning not cleared when namedBackups is zeroed post-login, leaving a stale subtitle warning in the add-more-accounts flow.
lib/storage.ts adds collectNamedBackups (stat-before + stat-after race detection), getNamedBackups, and restoreAccountsFromBackup with realpath-based containment guard, ENOENT guards for both backup root and file path, and ENOENT guard inside loadAccountsFromPath. path containment correctly rejects backup root itself (relativePath.length > 0).
test/storage-last-backup.test.ts new test file with comprehensive coverage: empty dir, zero-account backups, mtime sort, tie-break, race detection, non-JSON filtering, file disappearance during stat and during loadAccountsFromPath, UNC path containment (windows-gated), path escape rejection, backup-root-itself rejection, symlink escape, and missing-root guard.
test/codex-manager-cli.test.ts 13 new integration tests covering the full onboarding restore matrix: latest/manual/back flows, confirmation gate, EBUSY resilience, StorageError hint, persistence failure before/after write, stale index realignment, onboarding snapshot clearing after first oauth add, and manual refresh from existing-account menu.
lib/storage/migrations.ts adds "restore" to lastSwitchReason union in both AccountMetadataV1 and AccountMetadataV3 interfaces to match the new schema and storage normalization guard.
lib/schemas.ts adds "restore" to SwitchReasonSchema zod enum, consistent with accounts.ts, migrations.ts, and storage.ts normalization changes.
lib/ui/copy.ts adds all new UI strings for backup restore: headings, hints, help strings, confirm/loaded messages, and sync warning. formatters are functions rather than templates, keeping them testable.
lib/accounts.ts minor: adds "restore" to ManagedAccount.lastSwitchReason union type, consistent with schemas and migrations.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant CM as codex-manager (runAuthLogin)
    participant ST as storage (getNamedBackups)
    participant FS as filesystem
    participant RB as restoreAccountsFromBackup

    CM->>ST: loadNamedBackupsForOnboarding()
    ST->>FS: readdir(backupRoot)
    FS-->>ST: entries[]
    loop each .json entry
        ST->>FS: stat(candidatePath) [statsBefore]
        ST->>FS: loadAccountsFromPath(candidatePath)
        ST->>FS: stat(candidatePath) [statsAfter — race check]
    end
    ST-->>CM: NamedBackupSummary[] sorted by mtime desc

    CM->>U: promptOAuthSignInMode(latestBackup, warning?)
    U-->>CM: "restore-backup"

    CM->>U: promptBackupRestoreMode(latestBackup)
    U-->>CM: "latest" | "manual"

    alt manual
        CM->>U: promptManualBackupSelection(backups[])
        U-->>CM: selectedBackup
    end

    CM->>U: confirm("Load fileName (N accounts)?")
    U-->>CM: true

    CM->>RB: restoreAccountsFromBackup(path, {persist:false})
    RB->>FS: realpath(backupRoot) — ENOENT guard
    RB->>FS: realpath(path) — ENOENT guard
    RB->>RB: containment check (relative path)
    RB->>FS: loadAccountsFromPath(resolvedPath) — ENOENT guard
    RB-->>CM: AccountStorageV3

    CM->>CM: resolveActiveIndex(storage)
    CM->>CM: persistAndSyncSelectedAccount({switchReason:"restore"})
    CM->>FS: saveAccounts(storage) [single write]
    CM->>FS: setCodexCliActiveSelection(account)
    CM-->>U: "Loaded fileName (N accounts)."

    CM->>CM: continue loginFlow → dashboard
Loading

Comments Outside Diff (2)

  1. lib/codex-manager.ts, line 496-509 (link)

    activeIndexByFamily entries not bounds-checked when preserved

    shouldPreserveActiveIndexByFamily is computed before storage.activeIndex = targetIndex, so it correctly compares the original backup activeIndex against targetIndex. when they match — meaning resolveActiveIndex left the active index unchanged because the backup's index was already valid — the entire activeIndexByFamily map from the backup is written to disk without checking whether any of its values are within storage.accounts.length.

    concretely: a backup with activeIndex: 1 (valid, 2 accounts) but activeIndexByFamily: { codex: 5 } (out of bounds) passes the targetIndex === storage.activeIndex guard, skips the family-routing update block, and silently persists codex: 5. downstream routing code that reads activeIndexByFamily.codex would then land on a non-existent account — windows token-safety risk if the wrong slot gets refreshed.

    no vitest case in test/storage-last-backup.test.ts covers a restore where activeIndex is valid but activeIndexByFamily has an out-of-bounds entry (the existing "realigns restored family routing" test only covers the stale-activeIndex path where targetIndex !== storage.activeIndex).

    suggest adding a bounds clamp before the preserve block:

    const shouldPreserveActiveIndexByFamily =
        preserveActiveIndexByFamily &&
        !!storage.activeIndexByFamily &&
        targetIndex === storage.activeIndex &&
        Object.values(storage.activeIndexByFamily).every(
            (idx) => typeof idx === "number" && idx >= 0 && idx < storage.accounts.length,
        );
  2. lib/codex-manager.ts, line 4509 (link)

    stale onboardingBackupDiscoveryWarning shown after successful oauth login

    namedBackups = [] is cleared here after a successful oauth login, but onboardingBackupDiscoveryWarning is not. if backup discovery previously failed with EPERM and the user then signs in via browser/manual and chooses to add another account, the inner loop re-enters, latestNamedBackup resolves to null (correct), but promptOAuthSignInMode(null, onboardingBackupDiscoveryWarning) still injects the stale "Named backup discovery failed…" string into the subtitle — even though the user already has an account.

    the warning is only cleared via loadNamedBackupsForOnboarding(), which is not called on this path:

    no vitest case in test/codex-manager-cli.test.ts covers discovery-fails → oauth-success → add-another, so this path has zero regression coverage. worth adding a test that asserts the subtitle of the second sign-in prompt has no warning text.

Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: lib/codex-manager.ts
Line: 4508-4509

Comment:
**`onboardingBackupDiscoveryWarning` not cleared alongside `namedBackups`**

`namedBackups = []` suppresses the backup option in subsequent sign-in prompts, but `onboardingBackupDiscoveryWarning` retains its value from the earlier failed scan. if backup discovery previously threw `EPERM` (warning set), the user successfully adds an account via oauth, then picks "add more", the next call to `promptOAuthSignInMode(null, onboardingBackupDiscoveryWarning)` shows "Named backup discovery failed. Continuing with browser or manual sign-in only." in the subtitle — even though backup restore is no longer relevant and no backup option is displayed. windows users with temporarily locked backup dirs are the most likely to hit this.

```suggestion
			existingCount = count;
			namedBackups = [];
			onboardingBackupDiscoveryWarning = null;
```

no vitest case covers the "discovery fails then user adds account and chooses add-more" path — worth adding to `test/codex-manager-cli.test.ts` to lock down this behaviour.

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

Last reviewed commit: "fix(restore): clamp ..."

@chatgpt-codex-connector
Copy link
Copy Markdown

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 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 an onboarding “recover saved accounts” path to auth. storage now discovers named backups and can restore them. login flow offers restore when the account pool is empty. ui copy, docs, types, and tests updated to cover discovery, restore, persistence, and failure cases.

Changes

Cohort / File(s) Summary
Documentation
docs/features.md, docs/getting-started.md, docs/upgrade.md
added “onboarding backup restore” capability row and updated getting-started and upgrade notes to document the new sign-in menu option and newest/manual named-backup selection.
storage & backup
lib/storage.ts
added NamedBackupSummary and collectNamedBackups, exported getNamedBackups() and restoreAccountsFromBackup(). enforces path containment using isAbsolute/relative, filters empty/corrupt backups, sorts by mtime, and tolerates per-file failures. accepts "restore" as a cooldown reason.
login control flow
lib/codex-manager.ts
added "restore-backup" oauth mode and new prompt helpers (promptOAuthSignInMode, promptBackupRestoreMode, promptManualBackupSelection). refactored runOAuthFlow to accept explicit signInMode. implements restore-confirm → restoreAccountsFromBackuppersistAndSyncSelectedAccount(..., preserveActiveIndexByFamily?: true) path and error/retry handling. exported formatBackupSavedAt.
ui copy
lib/ui/copy.ts
expanded UI_COPY.oauth with get-started headings, restore labels, pluralizing hint builders (loadLastBackupHint, manualBackupHint, restoreBackupConfirm, restoreBackupLoaded), and restoreBackupSyncWarning. updated choose-mode strings.
types & schemas
lib/accounts.ts, lib/schemas.ts, lib/storage/migrations.ts
extended lastSwitchReason and SwitchReasonSchema to include "restore". updated migration type unions to accept "restore".
tests
test/codex-manager-cli.test.ts, test/storage-last-backup.test.ts
added mocks for getNamedBackups/restoreAccountsFromBackup and confirm. extensive CLI tests for onboarding restore flows, navigation, failures, and preserving state. new storage tests for discovery ordering, ENOENT races, persist vs non-persist restore, path containment, and missing-root errors.

Sequence Diagram(s)

sequenceDiagram
    participant user as user
    participant cli as codex manager
    participant ui as ui/prompts
    participant storage as storage

    user->>cli: run `codex auth login` (no accounts)
    cli->>storage: getNamedBackups()
    storage-->>cli: list of NamedBackupSummary or []
    alt backup found
        cli->>ui: promptOAuthSignInMode(latestBackup)
        ui-->>cli: "restore-backup"
        cli->>ui: promptBackupRestoreMode(latestBackup)
        ui-->>cli: "latest" or "manual"
        alt manual selected
            cli->>ui: promptManualBackupSelection(backups)
            ui-->>cli: chosen backup path
        end
        cli->>ui: confirm(restoreBackupConfirm)
        ui-->>cli: confirmed
        cli->>storage: restoreAccountsFromBackup(path)
        storage-->>cli: restored AccountStorage
        cli->>cli: persistAndSyncSelectedAccount(preserveActiveIndexByFamily=true)
        cli-->>user: show restored accounts / finish login
    else no backup or user chooses oauth
        cli->>ui: promptOAuthSignInMode(null)
        ui-->>cli: "browser" or "manual"
        cli->>cli: runOAuthFlow(forceNewLogin, signInMode)
        cli-->>user: complete oauth flow
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

flags:

  • missing windows path regression tests. see lib/storage.ts:1 and test/storage-last-backup.test.ts:1. add tests for windows-style separators, UNC paths, and isAbsolute/relative edge cases used in containment checks.
  • concurrency risk during readdir→stat→readFile races. see lib/storage.ts:1 and test/storage-last-backup.test.ts:1. consider stronger atomic checks, retries, or clearer handling when mtime changes between stat and read.
  • missing schema/version/integrity validation for restored backups. see lib/storage.ts:1. code currently accepts any non-empty accounts; add explicit schema/version or checksum validation before offering restore.
  • prompt/flow API surface changed. see lib/codex-manager.ts:1. runOAuthFlow signature changed and prompting moved up. audit other callers (outside login) for consistent usage and update call sites.
  • missing test asserting sync-warning UI when restore completes without auth sync. see lib/ui/copy.ts:1 and test/codex-manager-cli.test.ts:1. add an assertion that restoreBackupSyncWarning is displayed when sync did not occur.

suggested labels: bug

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed title follows conventional commits format (feat: lowercase imperative, 46 chars), clearly summarizes the main change of adding onboarding backup restore.
Description check ✅ Passed PR description is comprehensive and structured, covering summary, validation checklist, docs/governance items, and risk assessment; all required sections present.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/load-last-backup-onboarding
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/load-last-backup-onboarding
📝 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.

Comment thread lib/codex-manager.ts Outdated
Comment thread lib/storage.ts Outdated
Copy link
Copy Markdown
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: 5

🤖 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/storage.ts`:
- Around line 822-859: In getLatestNamedBackup, the silent catch that surrounds
loadAccountsFromPath/stat hides useful diagnostics; update the catch for the
per-file processing (around candidatePath handling in getLatestNamedBackup) to
log the caught error at debug or trace level (including candidatePath and
error.message/stack) before continuing so permission/parse errors are visible to
operators; ensure you reference candidatePath, entry.name and the functions
loadAccountsFromPath/fs.stat in the log message and continue skipping only after
logging.

In `@test/codex-manager-cli.test.ts`:
- Around line 3026-3070: The test configures loadAccountsMock with two identical
mockResolvedValueOnce calls which is fragile; replace the second
mockResolvedValueOnce with a persistent mockResolvedValue (or keep only one
mockResolvedValueOnce if only one call is expected) so extra storage reads won't
change behavior, and add an explicit assertion that the UI wasn't offered a
backup restore by checking promptLoginModeMock was called with choices that do
not include a "restore" option (while still asserting getLatestNamedBackupMock
was not called) when invoking runCodexMultiAuthCli(["auth","login"]). Use the
symbols loadAccountsMock, promptLoginModeMock, getLatestNamedBackupMock, and
runCodexMultiAuthCli to locate the edits.
- Around line 2966-3024: Add three regression tests: (1) simulate
restoreAccountsFromBackupMock rejecting with an EBUSY error and assert
runCodexMultiAuthCli returns non-zero or logs the platform-specific error
message (verify console output contains the EBUSY text), (2) simulate
setCodexCliActiveSelectionMock resolving/returning false and assert that the
restoreBackupSyncWarning (or the CLI warning path) is logged via console.warn
and that no successful-active-selection log is emitted, and (3) simulate
confirmMock returning false and assert that restoreAccountsFromBackupMock is not
called, getLatestNamedBackupMock is not acted upon for restore, and that the CLI
logs/skips the restore flow accordingly; locate these behaviors via the mocks
restoreAccountsFromBackupMock, setCodexCliActiveSelectionMock, confirmMock,
getLatestNamedBackupMock and exercise runCodexMultiAuthCli to verify outputs.
- Line 2996: Change the test's mocking of loadAccounts so it returns null only
for the first call and then returns the restored storage on the subsequent
read-back: replace the blanket loadAccountsMock.mockResolvedValue(null) with
loadAccountsMock.mockResolvedValueOnce(null) followed by a mockResolvedValue(or
mockResolvedValueOnce) that returns the expected persisted storage; this ensures
the behavior around persistAndSyncSelectedAccount() and the restart of the
loginFlow (the later loadAccounts() call) is exercised and verified.

In `@test/storage-last-backup.test.ts`:
- Around line 50-87: Add three deterministic unit tests for getLatestNamedBackup
to cover the untested edge cases: (1) when the backups directory is missing
ensure getLatestNamedBackup returns null; (2) when all named backup files exist
but each has zero accounts ensure it returns null; and (3) simulate a backup
file being deleted between reading its contents and statting it (e.g., create a
file, have loadAccountsFromPath succeed then remove the file before fs.stat) and
assert getLatestNamedBackup skips that file and still returns the correct result
or null as appropriate. Use the same patterns in this test file
(buildNamedBackupPath, fs.writeFile, fs.utimes) and reference
getLatestNamedBackup and loadAccountsFromPath to locate where to add the tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b8f87b1a-2024-4032-9b2c-cdd7eeae83c3

📥 Commits

Reviewing files that changed from the base of the PR and between b918aac and 63ba8f1.

📒 Files selected for processing (7)
  • docs/features.md
  • docs/getting-started.md
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/ui/copy.ts
  • test/codex-manager-cli.test.ts
  • test/storage-last-backup.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 (3)
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/getting-started.md
  • docs/features.md
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/storage-last-backup.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/ui/copy.ts
  • lib/storage.ts
  • lib/codex-manager.ts
🔇 Additional comments (16)
docs/features.md (1)

12-12: lgtm.

the new capability row accurately describes the onboarding backup restore feature and correctly references codex auth login as the primary entry point, consistent with the implementation in lib/codex-manager.ts.

docs/getting-started.md (1)

48-54: lgtm.

the updated flow documentation correctly describes the three sign-in options and their outcomes. the backup restore path is appropriately scoped to empty pool scenarios, matching the implementation in lib/codex-manager.ts.

lib/ui/copy.ts (1)

36-47: lgtm.

the new ui copy strings follow the existing pattern, correctly pluralize account counts, and don't leak any sensitive data. the restoreBackupSyncWarning message appropriately informs users when codex auth sync fails but local restore succeeds.

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

9-10: mock setup looks good.

the new storage mocks getLatestNamedBackupMock and restoreAccountsFromBackupMock are properly declared, registered in the module mock at test/codex-manager-cli.test.ts:119-120, and reset in beforeEach at test/codex-manager-cli.test.ts:440-441. the default return of null for getLatestNamedBackupMock at line 531 ensures existing tests aren't affected.

Also applies to: 119-120, 440-441


175-178: confirm mock added correctly.

the ../lib/ui/confirm.js mock at test/codex-manager-cli.test.ts:175-178 with reset at line 456-457 follows the existing pattern for ui mocks.

lib/codex-manager.ts (6)

56-60: lgtm - imports align with new storage exports.

getLatestNamedBackup and restoreAccountsFromBackup are both exported from lib/storage.ts. usage in the onboarding flow is correct.


1140-1144: interface subset is fine for ui consumption.

mtimeMs is omitted since it's only used internally for sorting in lib/storage.ts:857. no issue here.


1157-1167: conditional backup menu entry looks correct.

the spread pattern for conditionally adding the restore option is clean. hint text properly references filename and count without exposing tokens.


1453-1456: signature refactor simplifies flow control.

moving mode selection out of runOAuthFlow is correct - callers now handle cancel/restore before invoking the oauth exchange.


4273-4312: restore flow is well-guarded but has a minor type narrowing opportunity.

at lib/codex-manager.ts:4273, the condition signInMode === "restore-backup" && latestNamedBackup is safe, but typescript already knows latestNamedBackup is non-null when signInMode === "restore-backup" because you only offer that mode when backup exists (line 4257-4263). the runtime check is still good defensive code.

the persistAndSyncSelectedAccount call at line 4294-4299 correctly preserves the restored activeIndex from the backup via resolveActiveIndex(restoredStorage).


4121-4121: call site update correct.

runOAuthFlow(true, "browser") for refresh flow matches the expected behavior.

lib/storage.ts (2)

861-868: restore function correctly preserves active selection.

loadAccountsFromPathparseAndNormalizeStoragenormalizeAccountStorage preserves activeIndex and activeIndexByFamily with proper clamping. the test at test/storage-last-backup.test.ts:116-119 confirms this.

saveAccounts internally uses withStorageLock, so concurrent writes are safe.


118-123: interface is well-defined.

mtimeMs enables sorting by recency, accountCount is shown in ui hint. all fields populated at lib/storage.ts:846-851.

test/storage-last-backup.test.ts (3)

14-30: good retry helper for windows filesystem cleanup.

the EBUSY/EPERM/ENOTEMPTY retry set matches common windows errors. exponential backoff at 25 * 2^attempt is reasonable.


89-120: restore test correctly verifies replacement semantics.

expect(loaded?.accounts).toHaveLength(2) at line 117 confirms the original account was replaced, not merged. activeIndex preservation is verified at line 118.

one minor note: consider asserting loaded?.accounts[0]?.refreshToken === "refresh-1" as well to confirm both accounts are from the backup.


45-48: cleanup properly resets storage path.

setStoragePathDirect(null) before removeWithRetry ensures no lingering state between tests. the order matters here since cleanup happens before directory removal.

Comment thread lib/storage.ts Outdated
Comment thread test/codex-manager-cli.test.ts
Comment thread test/codex-manager-cli.test.ts Outdated
Comment thread test/codex-manager-cli.test.ts
Comment thread test/storage-last-backup.test.ts
Comment thread lib/codex-manager.ts Outdated
@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Mar 20, 2026

@greptile @coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2026

@ndycode sure, kicking off a review of pr #137 now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread lib/storage.ts Outdated
@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Mar 20, 2026

Addressed the actionable onboarding-restore review items on the current PR head.

What changed since the earlier bot summaries:

  • hardened interactive restore failure handling so EBUSY/restore errors no longer crash the CLI
  • hoisted backup discovery out of the inner auth loop so one onboarding attempt uses one stable backup list
  • switched onboarding section headings to safe sentinel values
  • removed the redundant getLatestNamedBackup export and kept getNamedBackups() as the single source of truth
  • added regression coverage for missing/empty/disappearing backups plus interactive restore failure and sync-false paths

Validation now passes:

  • npm run lint
  • npm run typecheck
  • npm run build
  • npm test
  • npm test -- test/documentation.test.ts

The remaining large CodeRabbit/Greptile summaries appear stale against the current branch head and still reference older symbols/behavior that are no longer present.

Comment thread lib/codex-manager.ts Outdated
Comment thread lib/codex-manager.ts Outdated
Copy link
Copy Markdown
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: 6

Caution

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

⚠️ Outside diff range comments (1)
docs/getting-started.md (1)

44-56: ⚠️ Potential issue | 🟠 Major

add an upgrade-note reference for this onboarding behavior change.

this section documents a changed login workflow, but there is no upgrade-note pointer in the updated path. add a short upgrade note reference near this flow so rollout expectations are explicit.

As per coding guidelines, docs/**: keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.

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

In `@docs/getting-started.md` around lines 44 - 56, Add a short upgrade-note
reference adjacent to the "Expected flow" block in docs/getting-started.md
summarizing the changed login workflow and linking to the upgrade notes (or
file/section where upgrade notes live), and explicitly call out any new npm
scripts and changed CLI flags related to the onboarding flow; update the
paragraph near the steps (the "Expected flow" heading) to include one-line
pointer like "See upgrade note: [summary/link] for rollout and CLI flag/npm
script changes."
♻️ Duplicate comments (1)
test/codex-manager-cli.test.ts (1)

3141-3214: ⚠️ Potential issue | 🟡 Minor

assert the warning branch when codex sync returns false.

line 3141 persists local restore correctly, but it does not assert the warning path when setCodexCliActiveSelectionMock returns false in test/codex-manager-cli.test.ts:3191. that leaves the sync-failure regression partially unverified.

proposed test hardening
 it("keeps the restored pool when Codex sync returns false", async () => {
+  const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
   const now = Date.now();
   setInteractiveTTY(true);
   let storageState: {
@@
   expect(setCodexCliActiveSelectionMock).toHaveBeenCalledWith(
     expect.objectContaining({
       accountId: "acc_warn",
       email: "warn@example.com",
     }),
   );
+  expect(warnSpy).toHaveBeenCalledWith(
+    expect.stringContaining("Codex auth sync did not complete"),
+  );
   expect(promptLoginModeMock).toHaveBeenCalledTimes(1);
   expect(selectMock).toHaveBeenCalledTimes(2);
 });

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. reject changes that mock real secrets or skip assertions.

🤖 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 3141 - 3214, The test currently
stubs setCodexCliActiveSelectionMock to resolve false but never asserts the
warning path; update the test to verify the warning branch was taken by
asserting the logger warning was called when setCodexCliActiveSelectionMock
returns false—specifically, add an expectation that the processLogger.warn (or
the module's logger.warn used by runCodexMultiAuthCli) mock was called (e.g.
toHaveBeenCalledTimes(1) and
toHaveBeenCalledWith(expect.stringMatching(/sync|warning|failed/i))) after
invoking runCodexMultiAuthCli, while keeping the existing assertions for
setCodexCliActiveSelectionMock, saveAccountsMock, promptLoginModeMock and
selectMock.
🤖 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/getting-started.md`:
- Around line 48-52: Update the docs/getting-started text so the sign-in option
label matches the CLI and tests: replace the phrase "Restore Saved Backup" (and
any dependent wording in the following step 4) with the exact CLI label "Recover
saved accounts" (as referenced in the test `Recover saved accounts` in
test/codex-manager-cli.test.ts:3048) to avoid user confusion; also ensure
README, SECURITY, and other docs are updated for consistency, add an upgrade
note describing the label/behavior change, and mention any new npm scripts
introduced by this change.

In `@lib/codex-manager.ts`:
- Around line 4406-4416: The restore flow is overwriting the backup's per-family
selection map because persistAndSyncSelectedAccount always resets
activeIndexByFamily to targetIndex; update the restore path so the original
restoredStorage.activeIndexByFamily is preserved: either add an option/flag to
persistAndSyncSelectedAccount (called from the restore code that calls
restoreAccountsFromBackup and resolveActiveIndex) to skip resetting
activeIndexByFamily, or change persistAndSyncSelectedAccount to only initialize
activeIndexByFamily when it is missing, leaving an existing map intact; adjust
the call-site that passes storage/targetIndex/parsed/switchReason so it sets
this new preserve flag (or ensures restoredStorage.activeIndexByFamily is
carried through), and add a vitest that creates a backup whose
activeIndexByFamily differs from activeIndex and verifies that after restore the
per-family map remains unchanged while other rotation/auth behaviors still run.
- Around line 4429-4432: The catch block in the restore flow is printing a
write-oriented hint by calling formatStorageErrorHint for all errors; change it
so the extra hint is only emitted for storage write errors (i.e., when error
instanceof StorageError from lib/storage.ts) or replace it with a read-specific
hint for restore errors from restoreAccountsFromBackup, and still log the
original backup restore failure message (the message variable) unchanged so the
vitest assertion still matches; specifically update the catch in
codex-manager.ts to gate the call to formatStorageErrorHint on error instanceof
StorageError (or call a new restore-read hint helper) and leave the
console.error(`Backup restore failed: ${message}`) as-is.

In `@lib/storage.ts`:
- Around line 874-885: Ensure restoreAccountsFromBackup only accepts files
inside the managed named-backup root by resolving and validating real paths:
call fs.realpath on the provided path and on the named-backup root (using the
same root resolution used by collectNamedBackups / getNamedBackupRoot), then
verify the resolved file path is contained within the resolved root before
calling loadAccountsFromPath and saveAccounts; if containment fails, throw an
error. Update restoreAccountsFromBackup to reference loadAccountsFromPath and
saveAccounts as before but perform the realpath containment check first, and add
a vitest in test/storage-last-backup.test.ts that creates a symlinked/escaped
backup path demonstrating the check rejects paths outside the named-backup root.

In `@test/codex-manager-cli.test.ts`:
- Around line 3430-3493: Add a regression test that covers the user backing out
of the manual backup picker by having selectMock resolve the same initial
choices but return null for the manual-pick selection; call
runCodexMultiAuthCli(["auth","login"]) and assert exitCode is 0, that
restoreAccountsFromBackupMock was NOT called with
"/mock/backups/manual-choice.json", that confirmMock was NOT called for "Load
manual-choice.json (1 account)?", and that saveAccountsMock call count reflects
no restore (e.g., toHaveBeenCalledTimes(0 or existing count)); use the same
mocks as the existing test (selectMock, restoreAccountsFromBackupMock,
saveAccountsMock, confirmMock, promptLoginModeMock) and mirror the setup for
getNamedBackupsMock and loadAccountsMock but replace mockResolvedValueOnce for
the third selectMock call with null to simulate backing out.

In `@test/storage-last-backup.test.ts`:
- Around line 145-164: The fs.stat spy created as statSpy in the test must be
restored even if getNamedBackups() throws; wrap the call to getNamedBackups()
and the subsequent assertions in a try/finally (or ensure finally-like cleanup)
so that statSpy.mockRestore() is executed in the finally block; locate the
mockImplementation on fs.stat and the call to getNamedBackups() in
test/storage-last-backup.test.ts and move the mockRestore() into the finally to
prevent the spy leaking into other tests.

---

Outside diff comments:
In `@docs/getting-started.md`:
- Around line 44-56: Add a short upgrade-note reference adjacent to the
"Expected flow" block in docs/getting-started.md summarizing the changed login
workflow and linking to the upgrade notes (or file/section where upgrade notes
live), and explicitly call out any new npm scripts and changed CLI flags related
to the onboarding flow; update the paragraph near the steps (the "Expected flow"
heading) to include one-line pointer like "See upgrade note: [summary/link] for
rollout and CLI flag/npm script changes."

---

Duplicate comments:
In `@test/codex-manager-cli.test.ts`:
- Around line 3141-3214: The test currently stubs setCodexCliActiveSelectionMock
to resolve false but never asserts the warning path; update the test to verify
the warning branch was taken by asserting the logger warning was called when
setCodexCliActiveSelectionMock returns false—specifically, add an expectation
that the processLogger.warn (or the module's logger.warn used by
runCodexMultiAuthCli) mock was called (e.g. toHaveBeenCalledTimes(1) and
toHaveBeenCalledWith(expect.stringMatching(/sync|warning|failed/i))) after
invoking runCodexMultiAuthCli, while keeping the existing assertions for
setCodexCliActiveSelectionMock, saveAccountsMock, promptLoginModeMock and
selectMock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6159b437-5334-4ec0-b320-bd54749a54dd

📥 Commits

Reviewing files that changed from the base of the PR and between 63ba8f1 and 052a314.

📒 Files selected for processing (7)
  • docs/features.md
  • docs/getting-started.md
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/ui/copy.ts
  • test/codex-manager-cli.test.ts
  • test/storage-last-backup.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 (3)
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/features.md
  • docs/getting-started.md
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
  • test/storage-last-backup.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/storage.ts
  • lib/ui/copy.ts
  • lib/codex-manager.ts
🔇 Additional comments (2)
lib/codex-manager.ts (1)

4389-4397: nice fix on the destructive restore gate.

lib/codex-manager.ts:4389-4397 now requires confirm() for both the latest and manual backup paths, which closes the earlier one-enter overwrite path.

docs/features.md (1)

12-12: looks good.

the new capability row is consistent with the onboarding restore behavior exercised in test/codex-manager-cli.test.ts:3047 and test/codex-manager-cli.test.ts:3487.

Comment thread docs/getting-started.md Outdated
Comment thread lib/codex-manager.ts
Comment thread lib/codex-manager.ts Outdated
Comment thread lib/storage.ts
Comment thread test/storage-last-backup.test.ts Outdated
Comment thread lib/codex-manager.ts Outdated
Comment thread lib/storage.ts Outdated
Copy link
Copy Markdown
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: 4

♻️ Duplicate comments (2)
lib/codex-manager.ts (1)

4433-4438: ⚠️ Potential issue | 🟡 Minor

print the storage error's own hint here.

lib/codex-manager.ts:4435-4437 is now correctly gated to StorageError, but it still formats the hint with selectedBackup.path. when saveAccounts() fails, lib/storage.ts:176-193 already carries the destination storage path and hint, so this currently points users at the source backup file instead of the file that actually failed to write. a small onboarding vitest around the persistence-failure branch would keep this from regressing.

proposed fix
-					if (error instanceof StorageError) {
-						console.error(formatStorageErrorHint(error, selectedBackup.path));
-					}
+					if (error instanceof StorageError) {
+						console.error(error.hint);
+					}

also drop the now-unused formatStorageErrorHint import at lib/codex-manager.ts:56.
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 4433 - 4438, The catch block for
StorageError is printing a hint for the wrong path (using selectedBackup.path) —
change the call to use the StorageError's own hint/path (e.g., use the
StorageError instance's properties or call formatStorageErrorHint with the
error's destination/hint rather than selectedBackup.path) so the message points
at the actual failed write (the saveAccounts destination); update the catch to
cast error to StorageError and pass the error's path/hint into
formatStorageErrorHint (or print error.hint directly), then remove the
now-unused formatStorageErrorHint import from the top of the file; add a small
vitest that simulates persistence failure in saveAccounts to assert the code
logs the storage error hint for the failing destination.
lib/storage.ts (1)

878-893: ⚠️ Potential issue | 🟠 Major

close the remaining restore check/use race.

lib/storage.ts:878-893 validates the backup path with realpath(), then reopens the same pathname later through loadAccountsFromPath(). a file or symlink swap between those two steps can still redirect the read outside the managed backup root, so the containment check is not atomic yet. please collapse validation + read into one guarded open/read path, and add a vitest beside test/storage-last-backup.test.ts:258-276 that swaps a validated backup before the read.

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/storage.ts` around lines 878 - 893, The TOCTOU comes from calling
fs.realpath() to validate the backup path then later calling
loadAccountsFromPath(path); fix by collapsing validation+read: open the target
with fs.promises.open using flags fs.constants.O_RDONLY |
fs.constants.O_NOFOLLOW (or open the parent dir and open the entry by name
without following symlinks), read the file contents from the returned FileHandle
(don't re-resolve the pathname), and pass the buffer/string into a new
loadAccountsFromBuffer/loadAccountsFromFd variant (or extend
loadAccountsFromPath to accept a Buffer/FileHandle) so the read happens from the
guarded open; keep the existing getNamedBackupRoot/resolvedBackupRoot check by
realpath-ing the resolvedBackupRoot once and ensure you only accept files opened
via the non-following FileHandle, then throw if opening fails or the handle
indicates the entry is outside the root; add a vitest alongside
test/storage-last-backup.test.ts (covering lines ~258-276) that swaps the backup
symlink between validation and read and assert the operation is rejected; ensure
logging changes avoid leaking tokens/emails and that new queue/error paths
handle EBUSY/429 per guidelines.
🤖 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/getting-started.md`:
- Around line 46-57: Update the onboarding steps in docs/getting-started.md so
they reflect the true empty-pool CLI flow implemented in lib/codex-manager.ts
(the branch starting at the sign-in menu around the logic near lines 4355-4369)
— remove or rework the “dashboard → Add New Account” path that only appears when
accounts exist (the branch near 4246-4253), and instead document that a
first-time login goes straight to the sign-in menu with the three options (Open
Browser (Easy), Manual / Incognito, Recover saved accounts); when mentioning the
backup directory use the repo’s default (~/.codex/multi-auth/backups) and also
document the CODEX_MULTI_AUTH_DIR override in the same paragraph; keep steps
concise and ensure the described menu flow (automatic newest valid backup vs.
manual newest-first selection, completing OAuth, restoring and syncing active
account, and confirming saved account) matches the behavior in the
sign-in/restore logic.

In `@lib/codex-manager.ts`:
- Around line 1144-1151: The function formatBackupSavedAt currently forces an
"en-US" locale; change its toLocaleString call to pass undefined (i.e., use new
Date(mtimeMs).toLocaleString(undefined, {...})) so it respects the runtime
locale, and update callers that render the backup restore hints (where
formatBackupSavedAt is used) if needed to accept the localized string unchanged;
additionally add a Vitest unit test that renders the backup hint output (the
code path that calls formatBackupSavedAt) and asserts the expected formatted
substring appears to provide coverage and prevent regressions.

In `@test/codex-manager-cli.test.ts`:
- Around line 3078-3110: Add a deterministic regression test that covers the
restore-mode returning "back" before the manual/latest flow by invoking
runCodexMultiAuthCli(["auth","login"]) and configuring selectMock to resolve the
sequence that includes "restore-backup" followed by "back" (instead of null),
asserting exitCode === 0 and that restoreAccountsFromBackupMock, confirmMock,
saveAccountsMock, and promptLoginModeMock were not called; place this alongside
the existing test that uses
selectMock.mockResolvedValueOnce("restore-backup")/("manual")/null so you cover
the alternate branch where restore mode itself yields "back" early.
- Around line 3112-3156: This test is non-deterministic because it doesn't force
TTY detection; ensure the interactive onboarding branch is exercised by stubbing
the process TTY flags before invoking runCodexMultiAuthCli: set
process.stdin.isTTY = true (and process.stdout.isTTY = true if your CLI checks
it) prior to importing or calling runCodexMultiAuthCli, and restore the original
values after the test; update the test that references runCodexMultiAuthCli,
promptLoginModeMock, loadAccountsMock and getNamedBackupsMock so the TTY stub is
in place for the duration of the test (and cleaned up) to deterministically
trigger the onboarding interactive path.

---

Duplicate comments:
In `@lib/codex-manager.ts`:
- Around line 4433-4438: The catch block for StorageError is printing a hint for
the wrong path (using selectedBackup.path) — change the call to use the
StorageError's own hint/path (e.g., use the StorageError instance's properties
or call formatStorageErrorHint with the error's destination/hint rather than
selectedBackup.path) so the message points at the actual failed write (the
saveAccounts destination); update the catch to cast error to StorageError and
pass the error's path/hint into formatStorageErrorHint (or print error.hint
directly), then remove the now-unused formatStorageErrorHint import from the top
of the file; add a small vitest that simulates persistence failure in
saveAccounts to assert the code logs the storage error hint for the failing
destination.

In `@lib/storage.ts`:
- Around line 878-893: The TOCTOU comes from calling fs.realpath() to validate
the backup path then later calling loadAccountsFromPath(path); fix by collapsing
validation+read: open the target with fs.promises.open using flags
fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW (or open the parent dir and open
the entry by name without following symlinks), read the file contents from the
returned FileHandle (don't re-resolve the pathname), and pass the buffer/string
into a new loadAccountsFromBuffer/loadAccountsFromFd variant (or extend
loadAccountsFromPath to accept a Buffer/FileHandle) so the read happens from the
guarded open; keep the existing getNamedBackupRoot/resolvedBackupRoot check by
realpath-ing the resolvedBackupRoot once and ensure you only accept files opened
via the non-following FileHandle, then throw if opening fails or the handle
indicates the entry is outside the root; add a vitest alongside
test/storage-last-backup.test.ts (covering lines ~258-276) that swaps the backup
symlink between validation and read and assert the operation is rejected; ensure
logging changes avoid leaking tokens/emails and that new queue/error paths
handle EBUSY/429 per guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3be4dcb6-c23d-44ec-b8ff-04475a340d07

📥 Commits

Reviewing files that changed from the base of the PR and between 052a314 and b91ba39.

📒 Files selected for processing (5)
  • docs/getting-started.md
  • lib/codex-manager.ts
  • lib/storage.ts
  • test/codex-manager-cli.test.ts
  • test/storage-last-backup.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 (3)
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/getting-started.md
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/storage-last-backup.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/storage.ts
  • lib/codex-manager.ts
🔇 Additional comments (2)
test/storage-last-backup.test.ts (1)

118-170: good regression coverage on disappearing backups and non-persist restore.

test/storage-last-backup.test.ts:118-236 covers the fs.stat race fallback and the persist: false restore path with deterministic filesystem setup. that is the right shape of vitest coverage for this onboarding change.

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. reject changes that mock real secrets or skip assertions.

Also applies to: 173-236

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

3257-3341: good coverage for windows ebusy and hardened restore-failure behavior.

the cases at test/codex-manager-cli.test.ts:3257-3341 are solid and directly exercise windows-style ebusy plus persistence failure without killing the onboarding session.

Comment thread docs/getting-started.md Outdated
Comment thread lib/codex-manager.ts Outdated
Comment thread test/codex-manager-cli.test.ts
Comment thread test/codex-manager-cli.test.ts
Comment thread lib/storage.ts Outdated
Comment thread lib/storage.ts Outdated
Copy link
Copy Markdown
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: 6

♻️ Duplicate comments (2)
test/storage-last-backup.test.ts (1)

297-315: ⚠️ Potential issue | 🟡 Minor

this still doesn't pin the realpath escape check.

test/storage-last-backup.test.ts:297 passes a path that already lives outside the managed root, so the assertion would still pass if lib/storage.ts:878-905 stopped resolving realpaths. the security-sensitive regression here is a symlink, or a win32 junction, inside the managed backup tree that resolves outside it. add that case so the containment guard is actually covered.

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.

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

3229-3302: ⚠️ Potential issue | 🟡 Minor

assert the onboarding sync warning when codex sync returns false.

test/codex-manager-cli.test.ts:3229-3302 proves the restored pool is persisted, but it never checks the warn path behind setCodexCliActiveSelectionMock.mockResolvedValueOnce(false) in lib/codex-manager.ts:4401-4450. if that warning regresses, this test still passes.

suggested fix
+		const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
 		const { runCodexMultiAuthCli } = await import("../lib/codex-manager.js");

 		const exitCode = await runCodexMultiAuthCli(["auth", "login"]);

 		expect(exitCode).toBe(0);
+		expect(warnSpy).toHaveBeenCalledWith(
+			expect.stringContaining("Codex auth sync did not complete"),
+		);
 		expect(restoreAccountsFromBackupMock).toHaveBeenCalledWith(
 			"/mock/backups/warn.json",
 			{ persist: false },
🤖 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 3229 - 3302, The test currently
verifies persistence but doesn't assert the onboarding-sync warning path
triggered when setCodexCliActiveSelectionMock.mockResolvedValueOnce(false) is
returned; update the test for runCodexMultiAuthCli to spy/mock the logger or the
specific warning helper used in the code (e.g., processLogger.warn or
showOnboardingSyncWarning) before invoking runCodexMultiAuthCli, then add an
assertion that the warning helper was called with the expected sync/onboarding
message after the call (use expect(...).toHaveBeenCalledWith/CalledTimes
matching the warning text or expect.stringContaining('sync' / 'onboard')).
Ensure you reference the same mocks used in the test
(setCodexCliActiveSelectionMock, promptLoginModeMock, selectMock) and place the
new assertion alongside the existing expects that check
restoreAccountsFromBackupMock and saveAccountsMock.
🤖 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/getting-started.md`:
- Around line 49-52: Update the docs/getting-started.md option text to read
"valid named backup" and add a short clarifier that the "Recover saved accounts"
menu entry only appears for backups that successfully parse and contain at least
one account (i.e., the filtering logic in lib/storage.ts that discards
unreadable or empty backup files); keep the existing mention of the storage root
override but ensure the phrasing matches this behavior so users don't expect
entries for empty or invalid backup files.

In `@lib/codex-manager.ts`:
- Around line 1144-1152: Add a Vitest unit test for the function
formatBackupSavedAt so its user-facing output is asserted and guarded against
regression: create a test (e.g., codex-manager.spec.ts) that imports
formatBackupSavedAt, stubs Date.prototype.toLocaleString (or uses vi.spyOn) to
return a deterministic string for the provided options, calls
formatBackupSavedAt with a fixed mtimeMs value, and asserts the returned string
matches the expected formatted output; ensure the stub is restored after the
test so other tests are unaffected.

In `@lib/storage.ts`:
- Around line 169-170: The sort comparator in candidates.sort currently orders
only by mtimeMs and is non-deterministic when mtimes tie; update the comparator
to add a stable secondary key (e.g., compare file name such as entry.name or
entry.filename lexicographically) so ties are deterministically ordered (keep
primary mtimeMs descending, then secondary name ascending/descending as desired)
in the candidates.sort call, and add/update the vitest at
test/storage-last-backup.test.ts (lines ~74-116) to assert deterministic order
for entries with identical mtimes; also ensure any related queue logic touched
by this change continues to handle EBUSY/429 retry semantics per lib/**
guidelines.

In `@test/codex-manager-cli.test.ts`:
- Around line 3554-3625: The test relies on a fixed getNamedBackupsMock payload
so a regression that re-fetches backups in the manual-restore branch could slip
by; update the test to pin single-discovery behavior by either asserting
getNamedBackupsMock was called exactly once after
runCodexMultiAuthCli(["auth","login"]) or make getNamedBackupsMock return a
different list on a second call (use mockResolvedValueOnce / mockResolvedValue)
so any additional fetch would fail the expectation; reference
getNamedBackupsMock and runCodexMultiAuthCli (and the manual restore branch in
lib/codex-manager.ts) when making the change.
- Around line 3304-3334: The test currently only asserts exitCode and the error
log; change it to also assert that the interactive menu continued after the
EBUSY failure by verifying the queued "cancel" selection was actually consumed —
e.g., after calling runCodexMultiAuthCli(["auth","login"]) assert on selectMock
usage (reference selectMock and runCodexMultiAuthCli) such as
expect(selectMock).toHaveBeenCalledTimes(3) or
expect(selectMock).toHaveBeenLastCalledWith("cancel") so the test fails if the
CLI returned early instead of processing the final menu choice.

In `@test/storage-last-backup.test.ts`:
- Around line 173-204: The tests for restoreAccountsFromBackup and the reload
path are only asserting activeIndex but not activeIndexByFamily, so add
assertions to both the returned "restored" object and the reloaded "loaded"
result to verify activeIndexByFamily was preserved; update the test cases that
call restoreAccountsFromBackup, loadAccounts and saveAccounts to expect the same
activeIndexByFamily map (e.g., { codex: 1 }) on both
restored.activeIndexByFamily and loaded.activeIndexByFamily to prevent
regressions where the per-family selection is dropped.

---

Duplicate comments:
In `@test/codex-manager-cli.test.ts`:
- Around line 3229-3302: The test currently verifies persistence but doesn't
assert the onboarding-sync warning path triggered when
setCodexCliActiveSelectionMock.mockResolvedValueOnce(false) is returned; update
the test for runCodexMultiAuthCli to spy/mock the logger or the specific warning
helper used in the code (e.g., processLogger.warn or showOnboardingSyncWarning)
before invoking runCodexMultiAuthCli, then add an assertion that the warning
helper was called with the expected sync/onboarding message after the call (use
expect(...).toHaveBeenCalledWith/CalledTimes matching the warning text or
expect.stringContaining('sync' / 'onboard')). Ensure you reference the same
mocks used in the test (setCodexCliActiveSelectionMock, promptLoginModeMock,
selectMock) and place the new assertion alongside the existing expects that
check restoreAccountsFromBackupMock and saveAccountsMock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: dffa8e39-9b6f-4639-8980-93d47d1e0842

📥 Commits

Reviewing files that changed from the base of the PR and between b91ba39 and c08918c.

📒 Files selected for processing (5)
  • docs/getting-started.md
  • lib/codex-manager.ts
  • lib/storage.ts
  • test/codex-manager-cli.test.ts
  • test/storage-last-backup.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 (3)
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/getting-started.md
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/storage-last-backup.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/storage.ts
  • lib/codex-manager.ts
🔇 Additional comments (12)
test/codex-manager-cli.test.ts (3)

9-10: good restore test scaffolding.

test/codex-manager-cli.test.ts:9-10,23,106-123,175-177,440-457,531-535 wires the new backup and confirm mocks into the suite cleanly. the resets plus restoreTTYDescriptors() keep the onboarding cases deterministic and isolated.

Also applies to: 23-23, 106-123, 175-177, 440-457, 531-535


2966-3094: good coverage on the new onboarding restore branches.

test/codex-manager-cli.test.ts:2966-3227 pins the single-save latest-restore path, localized timestamp hint, restore-mode back, manual-picker back, existing-pool skip, and discovery fallback against lib/codex-manager.ts:4273-4330.

Also applies to: 3096-3227


3336-3427: good failure-path and opt-out coverage around restore.

test/codex-manager-cli.test.ts:3336-3552 locks down StorageError hinting, pre-save failure handling, confirm rejection, and keeping manual refresh reachable. that gives lib/codex-manager.ts:4273-4450 much better regression protection.

Also applies to: 3429-3552

lib/codex-manager.ts (9)

4358-4366: backup discovery is correctly hoisted — looks good.

capturing namedBackups once before the inner loop and reusing the same snapshot for both "latest" and "manual" branches addresses the staleness asymmetry flagged earlier. the try/catch fallback to empty array is defensive and appropriate.


4553-4559: preserveActiveIndexByFamily logic is correct.

the condition !preserveActiveIndexByFamily || !storage.activeIndexByFamily ensures the family map is only overwritten when the flag is false or the map is missing. when restoring a backup with preserveActiveIndexByFamily: true, the original per-family selections remain intact.


4218-4227: refresh flow now prompts for sign-in mode — looks good.

decoupling mode selection from runOAuthFlow keeps the restore-backup path out of the refresh action. the early return for signInMode !== "browser" && signInMode !== "manual" is safe since "restore-backup" is only offered when backupOption is non-null (passed as null here).


56-63: new imports align with backup restore feature — no concerns.

getNamedBackups, restoreAccountsFromBackup, StorageError, NamedBackupSummary, and formatStorageErrorHint are all used in the new onboarding restore flow. no unused imports detected.


1209-1253: promptBackupRestoreMode handles non-TTY gracefully — looks good.

falling back to "latest" when not interactive is a sensible default for CI/headless scenarios. the hotkey mapping (1 → latest, 2 → manual, q → back) is consistent with other prompts.


1255-1289: promptManualBackupSelection handles empty list edge.

if backups is empty, backups[0] ?? null returns null in non-TTY mode, and the caller at lib/codex-manager.ts:4386-4388 continues the loop. this is safe.


1550-1553: runOAuthFlow signature change decouples mode prompting.

moving signInMode to a parameter simplifies the flow and enables the caller to decide the mode (including restore-backup branching). no regression risk since all callers now pass the mode explicitly.


4407-4418: regression test for per-family selection preservation during restore exists — comment accurate.

the restore flow test at test/codex-manager-cli.test.ts:2969 explicitly exercises the scenario: restoredStorage has activeIndex: 0 and activeIndexByFamily: { codex: 1 } (lines 2987-2988), then verifies post-restore that both values are correctly persisted (lines 3075-3079). the persistAndSyncSelectedAccount call with preserveActiveIndexByFamily: true (lib/codex-manager.ts:4417) correctly skips the reset logic at lines 4554-4559, keeping the backup's per-family map intact.


4431-4443: error handling correctly gates hint on StorageError.

the catch block checks error instanceof StorageError before calling formatStorageErrorHint, preventing misleading write-error hints for read-side failures (ENOENT, parse errors, etc.). the fallback console.error path handles other errors cleanly.

the toctou edge case where a backup file disappears between selection and restoreAccountsFromBackup is already covered. test/storage-last-backup.test.ts:118 tests the "disappears before stat" scenario and line 258 tests "disappears before restore realpath" — both explicitly set error.code = "ENOENT" and verify the fallback path. windows-specific file locking (EBUSY) is tested in test/codex-manager-cli.test.ts:3318 and 3355 with rejection handling and retry logic.

Comment thread docs/getting-started.md Outdated
Comment thread lib/codex-manager.ts Outdated
Comment thread lib/storage.ts Outdated
Comment thread test/codex-manager-cli.test.ts
Comment thread test/codex-manager-cli.test.ts
Comment thread test/storage-last-backup.test.ts
Comment thread lib/storage.ts Outdated
Copy link
Copy Markdown
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: 5

🤖 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/upgrade.md`:
- Line 73: The docs contain inconsistent escaping for the Windows backup path:
replace the double-backslash form `%CODEX_MULTI_AUTH_DIR%\\backups` found in
docs/upgrade.md with the single-backslash form `%CODEX_MULTI_AUTH_DIR%\backups`
to match docs/getting-started.md (standardize this single-backslash
representation across all documentation files mentioning the backup path).

In `@lib/codex-manager.ts`:
- Around line 4362-4368: The catch for getNamedBackups() in lib/codex-manager.ts
currently swallows all errors and sets namedBackups = [], which hides real
discovery failures (e.g. EPERM); update the catch to inspect error.code and only
treat ENOENT as “no backups” (set namedBackups = []), while for other codes
(EPERM, EACCES, etc.) rethrow or surface the error (or log and propagate) so the
restore option isn’t silently dropped; then add a vitest in
test/codex-manager-cli.test.ts that stubs/mocks getNamedBackups() to throw an
Error with code 'EPERM' and asserts the restore discovery fails (or that the
error is propagated) rather than returning an empty list.
- Around line 4358-4373: The loop captures existingCount and latestNamedBackup
once causing a stale "empty onboarding" state after an account is added; update
the loop so that after a successful account add you clear the empty-onboarding
snapshot or refresh refreshedStorage/namedBackups (re-run getNamedBackups and
recompute existingCount/latestNamedBackup or explicitly unset the
empty-onboarding marker) before the next promptOAuthSignInMode call (symbols:
refreshedStorage, existingCount, getNamedBackups, latestNamedBackup,
promptOAuthSignInMode and the account-add path around lines ~4474-4485). Also
add a vitest in test/codex-manager-cli.test.ts that starts with empty storage,
performs an add one account + "add another" flow, and asserts that restore is no
longer offered and that cancel returns to the main menu to verify the fix.

In `@lib/storage.ts`:
- Around line 878-932: Add a Windows-specific regression test in
test/storage-last-backup.test.ts that validates restoreAccountsFromBackup's
realpath-based containment logic for UNC and cross-drive scenarios: create a
backup root (getNamedBackupRoot) and then craft both an allowed UNC path inside
that root and a disallowed UNC or cross-drive path outside it, call
restoreAccountsFromBackup(path, { persist: true|false }) and assert the inside
path succeeds (loads normalized accounts and optionally persists via
saveAccounts) while the outside path throws the containment Error; make the test
skip on non-Windows/unsupported environments and reuse
loadAccountsFromPath/saveAccounts mocks to avoid touching the real FS.
Optionally add a separate test that simulates concurrent restore EBUSY/429 to
drive adding retry logic to restoreAccountsFromBackup if you plan to implement
retries.

In `@test/storage-last-backup.test.ts`:
- Around line 377-391: The fs.readFile spy only checks String(path) ===
backupPath so it misses the canonicalized path used after fs.realpath; before
installing the spy call the real fs.realpath on backupPath to get
resolvedBackupPath and update the spy to throw ENOENT when path equals either
backupPath or resolvedBackupPath; adjust the mockImplementation in the test
(around readFileSpy creation) so it compares against both values and still
delegates to originalReadFile otherwise, ensuring loadAccountsFromPath and its
fs.readFile call hit the ENOENT branch across platforms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9bdd954b-74c2-43b9-b25f-4f05ed57533b

📥 Commits

Reviewing files that changed from the base of the PR and between c08918c and 6d8c584.

📒 Files selected for processing (9)
  • docs/getting-started.md
  • docs/upgrade.md
  • lib/accounts.ts
  • lib/codex-manager.ts
  • lib/schemas.ts
  • lib/storage.ts
  • lib/storage/migrations.ts
  • test/codex-manager-cli.test.ts
  • test/storage-last-backup.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 (3)
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/upgrade.md
  • docs/getting-started.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/schemas.ts
  • lib/accounts.ts
  • lib/storage/migrations.ts
  • lib/storage.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
  • test/storage-last-backup.test.ts
🔇 Additional comments (25)
lib/schemas.ts (1)

85-91: lgtm — "restore" added to the schema enum.

this aligns with the storage layer and ManagedAccount type expansion. the narrower signature on markSwitched at lib/accounts.ts:573 is intentional since restore reasons are assigned via persistAndSyncSelectedAccount, not the rotation path.

lib/accounts.ts (1)

95-100: type expansion is consistent with schema and storage layer.

markSwitched at lib/accounts.ts:573 intentionally keeps a narrower union since restore reasons are set outside the rotation path. no action needed.

lib/storage/migrations.ts (2)

28-33: type expansion for v1 metadata is consistent.

aligns with SwitchReasonSchema and ManagedAccount changes. migration logic at lib/storage/migrations.ts:102 passes lastSwitchReason through unchanged, which remains correct.


58-63: type expansion for v3 metadata is consistent.

matches the schema and accounts layer. no runtime changes needed in migrateV1ToV3.

docs/getting-started.md (1)

46-60: onboarding restore flow documented correctly.

steps match the cli behavior in lib/codex-manager.ts — empty pool routes to sign-in menu, Recover saved accounts appears only when valid named backups exist, and the newest-first picker is documented. the filtering behavior at line 57 matches lib/storage.ts:141-147.

lib/storage.ts (2)

118-175: named backup discovery looks solid.

collectNamedBackups handles the key edge cases:

  • lib/storage.ts:135-136 returns [] for missing backup root (ENOENT), re-throws EPERM/EACCES.
  • lib/storage.ts:169-173 sorts by mtimeMs descending with fileName as tiebreaker for determinism on windows/coarse-timestamp filesystems.
  • lib/storage.ts:155-164 logs failures at debug level without leaking sensitive account data.

the .json check at line 142 uses toLowerCase() which handles mixed-case extensions on case-insensitive filesystems.


2188-2189: type guard updated to match schema.

isSwitchReason now validates all five values including "restore", consistent with lib/schemas.ts:85-91.

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

9-10: lgtm on mock setup.

mock additions for getNamedBackupsMock, restoreAccountsFromBackupMock, confirmMock, and logger methods follow the existing test patterns. the default confirmMock returning true aligns with the common happy-path scenario.

Also applies to: 23-23, 33-43


179-181: lgtm.


444-445: proper test isolation.

resetting getNamedBackupsMock, restoreAccountsFromBackupMock, and confirmMock in beforeEach with sensible defaults (empty backups, confirm=true) ensures each test starts from a clean state.

Also applies to: 460-461, 535-535


546-564: nice deterministic locale test.

spying on Date.prototype.toLocaleString and verifying the exact options passed ensures the formatter uses the intended locale settings without depending on the test runner's locale.


2990-3118: solid restore happy-path coverage.

the test verifies the full restore flow: menu presentation, hint formatting, confirm dialog, restoreAccountsFromBackup with persist: false, storage persistence via withAccountStorageTransaction, and codex cli sync. the locale spy in try/finally ensures cleanup.

one minor note: restoreAccountsFromBackupMock.mockResolvedValue(...) at test/codex-manager-cli.test.ts:3055 could be mockResolvedValueOnce since restore is expected to be called exactly once, but since the test asserts restoreAccountsFromBackupMock call count implicitly through the flow, this is acceptable.


3120-3145: good regression for back navigation.

addresses the prior review request for testing the restore-mode "back" path. confirms no side effects occur when user backs out of the restore menu.


3147-3179: manual picker back-out covered.

verifies that returning null from the backup picker correctly aborts the restore flow without calling restoreAccountsFromBackup or saveAccounts.


3181-3227: correctly skips restore when accounts exist.

the test ensures getNamedBackupsMock is never called when the account pool is non-empty, which matches the pr objective that restore appears only during onboarding with an empty pool.


3229-3255: good graceful degradation test.

verifies that when getNamedBackups throws (e.g., EACCES on windows), the cli falls back to normal sign-in without crashing and logs the error at debug level. this addresses the greptile comment about non-ENOENT errors being silently swallowed—the test confirms loggerDebugMock is called with the error context at test/codex-manager-cli.test.ts:3251-3254.


3257-3334: sync failure warning path verified.

addresses the prior review request for testing when setCodexCliActiveSelectionMock returns false. the test confirms the restored pool is still persisted locally and the warning is logged, matching the restoreBackupSyncWarning copy behavior.


3336-3369: ebusy menu survival regression is solid.

uses a windows-style path C:/mock/backups/locked.json and verifies selectMock is called 3 times, confirming the cli session survives the EBUSY failure and processes the queued "cancel" selection. this addresses the prior review concern at test/codex-manager-cli.test.ts:3367.


3371-3408: storage error hint deduplication verified.

verifies that StorageError with a hint is printed exactly once via formatStorageErrorHint, preventing duplicate or confusing error output.


3410-3462: persistence failure keeps session alive.

verifies that when saveAccounts fails after a successful restore, the cli logs the error and returns to the onboarding menu rather than crashing or proceeding with stale state. setCodexCliActiveSelectionMock not being called confirms the flow didn't complete.


3464-3490: confirmation rejection path covered.

addresses the prior review request for testing when confirmMock returns false. verifies the restore is cleanly skipped without calling restoreAccountsFromBackup or saveAccounts.


3492-3523: manual picker confirmation rejection covered.

verifies the manual backup selection path also respects user declining the confirmation dialog.


3525-3587: existing refresh path preserved.

good sanity check that the manual refresh flow from the existing-account menu still works alongside the new restore functionality.


3589-3661: single-discovery pinned in manual picker test.

expect(getNamedBackupsMock).toHaveBeenCalledTimes(1) at test/codex-manager-cli.test.ts:3647 ensures the backup discovery is hoisted and not re-fetched when entering the manual picker branch, addressing the prior review concern.


3041-3054: the tied-mtime regression test already exists at test/storage-last-backup.test.ts:118. it sets identical mtimes and verifies deterministic filename-based ordering with localeCompare. the cli test correctly uses distinct mtimes (now vs now - 60_000) for its specific case; the storage layer handles ties at lib/storage.ts:170-171.

Comment thread docs/upgrade.md Outdated
Comment thread lib/codex-manager.ts Outdated
Comment thread lib/codex-manager.ts Outdated
Comment thread lib/storage.ts
Comment thread test/storage-last-backup.test.ts
@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Mar 20, 2026

@greptile @coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2026

@ndycode sure, kicking off a review of the current head of pr #137 now. will evaluate the hardened restore failure handling, hoisted backup discovery, sentinel heading values, consolidated getNamedBackups() export, and the new regression coverage you called out.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
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: 3

♻️ Duplicate comments (3)
docs/getting-started.md (1)

53-54: ⚠️ Potential issue | 🟡 Minor

point the docs at the actual menu action.

docs/getting-started.md:53-54 says users choose Recover saved accounts, but test/codex-manager-cli.test.ts:3072-3085 shows that string is the heading. the selectable row is Restore Saved Backup. right now the steps send users to a non-actionable label.

proposed fix
-   - `Recover saved accounts` when the current pool is empty and at least one valid named backup exists under `~/.codex/multi-auth/backups` by default, or under `%CODEX_MULTI_AUTH_DIR%\backups` if you override the storage root with `CODEX_MULTI_AUTH_DIR`
-3. If you choose `Recover saved accounts`, the next menu lets you either:
+   - `Restore Saved Backup` under the `Recover saved accounts` heading when the current pool is empty and at least one valid named backup exists under `~/.codex/multi-auth/backups` by default, or under `%CODEX_MULTI_AUTH_DIR%\backups` if you override the storage root with `CODEX_MULTI_AUTH_DIR`
+3. If you choose `Restore Saved Backup`, the next menu lets you either:

As per coding guidelines, docs/**: keep README, SECURITY, and docs consistent with actual CLI flags and workflows.

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

In `@docs/getting-started.md` around lines 53 - 54, Update the documentation to
point at the actual actionable CLI menu label: change the user-facing phrase
"Recover saved accounts" to the selectable row text "Restore Saved Backup" in
the getting-started docs so the steps map to the real menu item; ensure any
nearby descriptive text and the sequence that follows still read correctly (the
test reference in test/codex-manager-cli.test.ts shows "Recover saved accounts"
is a heading, not the selectable action, so align the doc string with "Restore
Saved Backup").
lib/codex-manager.ts (1)

1156-1208: ⚠️ Potential issue | 🟠 Major

keep backup discovery failures visible across the next prompt render.

lib/codex-manager.ts:4368-4377 only emits a transient console.warn when getNamedBackups() fails, but the next onboarding prompt at lib/codex-manager.ts:4383 goes through promptOAuthSignInMode() with clearScreen: true at lib/codex-manager.ts:1194. in the interactive path that still degrades EPERM/EACCES into the same user experience as "no backups found", because the warning is wiped before the user can act on it. keep this state in the prompt subtitle/help or surface a durable error banner instead of clearing it away. please add a vitest in test/codex-manager-cli.test.ts that forces getNamedBackups() to throw EPERM and asserts the failure stays visible after the next prompt render. 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: 4360-4379

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

In `@lib/codex-manager.ts` around lines 1156 - 1208, The transient
backup-discovery warning from getNamedBackups() is being cleared by the next
interactive select (promptOAuthSignInMode -> select with clearScreen:true);
update the flow so the error is persisted into the prompt UI instead of a
console.warn (e.g., return or attach an error string to the prompt's
subtitle/help or pass an error/banner item into the MenuItem list used by
promptOAuthSignInMode) and ensure promptOAuthSignInMode consumes that persisted
error (check the select call options: clearScreen: true, subtitle/help, and
onInput). Add a Vitest in test/codex-manager-cli.test.ts that stubs
getNamedBackups() to throw an EPERM and asserts the resulting prompt still
displays the error message after render (use the promptOAuthSignInMode/select
hooks or captured subtitle text), cite/adjust any affected tests, and avoid
logging sensitive data (tokens/emails) while adding durable error handling for
EPERM/EACCES/EBUSY/429 scenarios.
test/storage-last-backup.test.ts (1)

470-521: ⚠️ Potential issue | 🟠 Major

gate the unc regression to win32 or it stops testing the intended branch.

test/storage-last-backup.test.ts:470-521 drives windows UNC strings through lib/storage.ts:912-916, which uses platform-native node:path. on non-windows runners, relative() and isAbsolute() do not treat \\\\server\\share\\... as a windows UNC path, so this test can fail or give a false signal while never exercising the real windows containment behavior. skip it on process.platform !== "win32" or move the assertion behind a win32-scoped helper. 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. reject changes that mock real secrets or skip assertions.

#!/bin/bash
node - <<'NODE'
const path = require('node:path');
const from = '\\\\server\\share\\codex\\backups';
const inside = '\\\\server\\share\\codex\\backups\\backup-unc-inside.json';

console.log({ platform: process.platform });
console.log({
  relative: path.relative(from, inside),
  isAbsoluteFrom: path.isAbsolute(from),
  isAbsoluteInside: path.isAbsolute(inside),
});
NODE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/storage-last-backup.test.ts` around lines 470 - 521, The UNC-path test
"accepts UNC-resolved backups inside the managed root and rejects cross-share
escapes" exercises Windows-specific path behavior but runs on non-Windows
runners; guard it so it only executes on win32: at the start of that test (the
one that calls buildNamedBackupPath and restoreAccountsFromBackup and mocks
fs.realpath/fs.readFile) add a platform gate using Vitest (e.g., if
(process.platform !== "win32") { test.skip(); } or an early return/skip) so the
UNC strings and node:path semantics are only exercised on Windows, leaving the
rest of the test suite deterministic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/codex-manager-cli.test.ts`:
- Around line 3098-3104: The test only asserts active indices but must also
assert the persisted lastSwitchReason equals "restore"; update the expectation
on the saveAccountsMock call (the argument inspected at
saveAccountsMock.mock.calls[0]?.[0]) to include an assertion for
lastSwitchReason: "restore" (e.g., extend the objectContaining to include
lastSwitchReason) so changes in lib/schemas.ts, lib/accounts.ts, and
lib/storage/migrations.ts that add the "restore" literal are validated by the
test.
- Line 3746: The manual-picker single-attempt restore test is still asserting
two getNamedBackups() calls, allowing a second scan; change the assertion in the
single-attempt manual restore flow to expect one call to getNamedBackupsMock (so
the manual picker is pinned to a single discovery), leaving the separate
refresh-on-reopen test unchanged; locate the assertion using the symbol
getNamedBackupsMock in the single-attempt manual restore test and update the
toHaveBeenCalledTimes(2) → toHaveBeenCalledTimes(1).

In `@test/storage-last-backup.test.ts`:
- Around line 524-542: The test never ensures the named-backup root exists, so
the call to restoreAccountsFromBackup hits the "missing-root" branch instead of
the containment guard; before calling
restoreAccountsFromBackup(escapedBackupPath) create the parent directory for
buildNamedBackupPath("backup-inside-root") (i.e.,
mkdir(dirname(buildNamedBackupPath(...)), { recursive: true })) so the
named-backup root is present and the containment check in lib/storage.ts
(restoreAccountsFromBackup) is exercised deterministically under vitest.

---

Duplicate comments:
In `@docs/getting-started.md`:
- Around line 53-54: Update the documentation to point at the actual actionable
CLI menu label: change the user-facing phrase "Recover saved accounts" to the
selectable row text "Restore Saved Backup" in the getting-started docs so the
steps map to the real menu item; ensure any nearby descriptive text and the
sequence that follows still read correctly (the test reference in
test/codex-manager-cli.test.ts shows "Recover saved accounts" is a heading, not
the selectable action, so align the doc string with "Restore Saved Backup").

In `@lib/codex-manager.ts`:
- Around line 1156-1208: The transient backup-discovery warning from
getNamedBackups() is being cleared by the next interactive select
(promptOAuthSignInMode -> select with clearScreen:true); update the flow so the
error is persisted into the prompt UI instead of a console.warn (e.g., return or
attach an error string to the prompt's subtitle/help or pass an error/banner
item into the MenuItem list used by promptOAuthSignInMode) and ensure
promptOAuthSignInMode consumes that persisted error (check the select call
options: clearScreen: true, subtitle/help, and onInput). Add a Vitest in
test/codex-manager-cli.test.ts that stubs getNamedBackups() to throw an EPERM
and asserts the resulting prompt still displays the error message after render
(use the promptOAuthSignInMode/select hooks or captured subtitle text),
cite/adjust any affected tests, and avoid logging sensitive data (tokens/emails)
while adding durable error handling for EPERM/EACCES/EBUSY/429 scenarios.

In `@test/storage-last-backup.test.ts`:
- Around line 470-521: The UNC-path test "accepts UNC-resolved backups inside
the managed root and rejects cross-share escapes" exercises Windows-specific
path behavior but runs on non-Windows runners; guard it so it only executes on
win32: at the start of that test (the one that calls buildNamedBackupPath and
restoreAccountsFromBackup and mocks fs.realpath/fs.readFile) add a platform gate
using Vitest (e.g., if (process.platform !== "win32") { test.skip(); } or an
early return/skip) so the UNC strings and node:path semantics are only exercised
on Windows, leaving the rest of the test suite deterministic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d2e115b-169d-4198-810c-b498a6f091ac

📥 Commits

Reviewing files that changed from the base of the PR and between c08918c and f4b63f3.

📒 Files selected for processing (9)
  • docs/getting-started.md
  • docs/upgrade.md
  • lib/accounts.ts
  • lib/codex-manager.ts
  • lib/schemas.ts
  • lib/storage.ts
  • lib/storage/migrations.ts
  • test/codex-manager-cli.test.ts
  • test/storage-last-backup.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 (3)
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/upgrade.md
  • docs/getting-started.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/accounts.ts
  • lib/storage/migrations.ts
  • lib/schemas.ts
  • lib/storage.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/storage-last-backup.test.ts
  • test/codex-manager-cli.test.ts
🔇 Additional comments (1)
lib/storage.ts (1)

125-184: good hardening on discovery and restore paths.

lib/storage.ts:125-184 now keeps named-backup discovery deterministic and race-tolerant, and lib/storage.ts:887-941 gates restore through realpath containment while letting the caller own the single write with persist: false. test/storage-last-backup.test.ts:68-468,544-609 covers the empty-root, tie-break, disappearing-file, persist-split, and path-escape cases.

Also applies to: 887-941

Comment thread test/codex-manager-cli.test.ts
Comment thread test/codex-manager-cli.test.ts Outdated
Comment thread test/storage-last-backup.test.ts
@ndycode ndycode added the passed label Mar 20, 2026
@ndycode ndycode merged commit 49d3ae5 into main Mar 20, 2026
2 checks passed
@ndycode ndycode deleted the feat/load-last-backup-onboarding branch March 24, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant