Skip to content

feat(auth): add backup restore manager#76

Open
ndycode wants to merge 37 commits intogit-plan/01-reset-safetyfrom
git-plan/02-backup-restore-manager
Open

feat(auth): add backup restore manager#76
ndycode wants to merge 37 commits intogit-plan/01-reset-safetyfrom
git-plan/02-backup-restore-manager

Conversation

@ndycode
Copy link
Owner

@ndycode ndycode commented Mar 12, 2026

Summary

  • add named-backup restore flows to codex auth login, including the recovery path when no accounts are saved
  • harden restore/list/import behavior around backup-path containment, transient filesystem retries, and restore-time revalidation
  • sync changed restores back into Codex CLI auth state and extend targeted regressions around restore safety, cache reuse, login recovery, and retry exhaustion

Why

The restore-manager flow needs to stay inside the backups boundary, survive transient filesystem contention on Windows, and keep Codex CLI auth state aligned after successful restores.

Docs / Governance

  • updated docs/reference/storage-paths.md for the named-backup restore path and workflow
  • no README, getting-started, upgrade, security, or contributing changes were required for this PR scope
  • no new secrets, environment variables, or policy exceptions were introduced

Validation

  • npx vitest run test/storage.test.ts test/codex-manager-cli.test.ts test/cli.test.ts --maxWorkers=1
  • npx eslint lib/storage.ts lib/codex-manager.ts test/storage.test.ts test/codex-manager-cli.test.ts

Risk

  • medium: touches restore path containment, retry timing under transient filesystem locks, and restore-time account import accounting
  • repo-wide npm run typecheck still has pre-existing unrelated failures outside this PR scope

Rollback

  • revert PR #76 commits from git-plan/02-backup-restore-manager
  • if needed, specifically revert 94c18b8d297663be987dfd1fb6079d88751b0398 to drop the latest restore containment and retry hardening

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 wires a named-backup restore manager into codex auth login, adds path containment hardening, transient filesystem retry logic, and syncs codex cli auth state after a successful restore. the login loop is also flattened so the restore option is reachable even when no accounts are saved.

key changes:

  • runBackupRestoreManager in lib/codex-manager.ts: lists backups, assesses each with concurrency cap (NAMED_BACKUP_ASSESS_CONCURRENCY=4), re-assesses selected backup before confirmation (TOCTOU guard), calls assertNamedBackupRestorePath a second time before importAccounts, then syncs codex cli state
  • assertNamedBackupRestorePath / resolveNamedBackupRestorePath in lib/storage.ts: path containment via realpathSync.native + relative() check; symlinks filtered out earlier by listNamedBackups and findExistingNamedBackupPath
  • retryTransientFilesystemOperation: 7-attempt exponential backoff with jitter for EBUSY, EAGAIN, ENOTEMPTY, and EPERM (win32 only for the last)
  • importAccounts now returns a changed boolean and always deduplicates before checking the account limit; supports a metadata-refresh path (imported=0, changed=true) when backup values are newer
  • test/storage.test.ts afterEach now uses removeWithRetry — the previously flagged windows flakiness issue is addressed
  • 13+ new cli orchestration tests and integration tests in storage.test.ts

issues noted:

  • EBUSY and ENOTEMPTY are retried on all platforms, not scoped to win32 — on linux these errors from readFile/readdir are typically non-transient and silently adding 2-3s of retry delay could mask real errors
  • assertNamedBackupRestorePath returns the pre-realpath resolvedPath rather than the symlink-resolved containedPath — creates a narrow TOCTOU window between containment validation and importAccounts read, though symlink filtering earlier in the pipeline largely mitigates this
  • no vitest coverage for retryTransientFilesystemOperation exhausting all 7 attempts — the PR description lists retry exhaustion as a regression target but the new test only validates a single-failure recovery

Confidence Score: 3/5

  • mergeable with low-priority fixes: cross-platform retry scope and a missing exhaustion test are the main remaining gaps after prior containment/error-handling issues were addressed
  • the path traversal and unhandled error issues flagged in the previous review appear addressed. the double assertNamedBackupRestorePath defense-in-depth, TOCTOU re-assessment, and symlink filtering are all in place. three style-level issues remain: EBUSY/ENOTEMPTY retry scoping, the assertNamedBackupRestorePath returning pre-realpath path, and missing retry exhaustion test coverage. none are blocking correctness on the happy path, but the retry scoping and exhaustion gap are real risks on windows ci and non-windows environments respectively.
  • lib/storage.ts — retry error code scoping and assertNamedBackupRestorePath return value; test/storage.test.ts — missing retry exhaustion coverage

Important Files Changed

Filename Overview
lib/storage.ts largest change in the PR — adds named backup listing, assessment, restore, and path containment logic. assertNamedBackupRestorePath and retryTransientFilesystemOperation are the two key safety mechanisms; both have minor issues flagged above (return value of realpath-resolved path, cross-platform EBUSY retry scope). logic for importAccounts is correct and now carries a changed field.
lib/codex-manager.ts adds runBackupRestoreManager and flattens the runAuthLogin login loop to expose the restore path when no accounts are saved. re-assessment + double assertNamedBackupRestorePath call before importAccounts is correct defense-in-depth. error handling around runBackupRestoreManager is wrapped in try/catch so no crash risk in the menu loop. previously flagged unhandled error issue appears addressed.
test/storage.test.ts exports/imports suite afterEach now uses removeWithRetry (addresses prior EBUSY/EPERM flakiness concern). new named backup tests cover listing, assessment, restore, and containment. missing coverage: retryTransientFilesystemOperation exhaustion (all 7 attempts fail).
test/codex-manager-cli.test.ts adds 13+ scenario tests for the restore manager: empty backup dir, path escape, confirmation decline, TOCTOU reassessment, concurrency cap, metadata refresh wording, quota-refresh ordering. coverage is thorough for the menu orchestration layer.
lib/ui/auth-menu.ts adds restore-backup action type and inserts a "Recovery" heading section with the restore menu item. change is minimal and low-risk.
lib/ui/copy.ts adds UI copy for restore confirm, success, and refresh messages. fallback prompt string updated to include (u) restore backup. copy is consistent with the restore flow logic in runBackupRestoreManager.
lib/cli.ts adds restore-backup to LoginMode union and fallback input aliases (u, backup, restore, restore-backup). straightforward extension with no risk surface.
test/cli.test.ts adds regression for all four fallback aliases resolving to restore-backup. clean and correct.
docs/reference/storage-paths.md adds named backup path entries and a restore workflow section. accurate and matches the implementation.

Sequence Diagram

sequenceDiagram
    participant User
    participant AuthMenu
    participant BackupRestoreManager
    participant Storage
    participant ImportAccounts
    participant CodexCLI

    User->>AuthMenu: select "Restore From Backup"
    AuthMenu->>BackupRestoreManager: runBackupRestoreManager()
    BackupRestoreManager->>Storage: listNamedBackups({ candidateCache })
    Storage-->>BackupRestoreManager: NamedBackupMetadata[]

    loop for each chunk (ASSESS_CONCURRENCY=4)
        BackupRestoreManager->>Storage: assessNamedBackupRestore(name, { currentStorage, candidateCache })
        Storage-->>BackupRestoreManager: BackupRestoreAssessment
    end

    BackupRestoreManager->>User: select() — show backup menu
    User-->>BackupRestoreManager: { type: "restore", assessment }

    Note over BackupRestoreManager: re-assess with fresh loadAccounts() (TOCTOU guard)
    BackupRestoreManager->>Storage: assessNamedBackupRestore(name, { currentStorage: await loadAccounts() })
    Storage-->>BackupRestoreManager: latestAssessment

    alt latestAssessment.eligibleForRestore === false
        BackupRestoreManager->>User: show ineligible message, return
    else eligible
        BackupRestoreManager->>User: confirm(merge summary)
        User-->>BackupRestoreManager: confirmed

        BackupRestoreManager->>Storage: assertNamedBackupRestorePath(path, backupDir)
        Note over Storage: containment check via realpathSync
        Storage-->>BackupRestoreManager: validatedPath

        BackupRestoreManager->>ImportAccounts: importAccounts(validatedPath)
        Note over ImportAccounts: retryTransientFilesystemOperation (7 attempts)<br/>withAccountStorageTransaction → dedup → persist
        ImportAccounts-->>BackupRestoreManager: { imported, skipped, total, changed }

        alt result.changed === false
            BackupRestoreManager->>User: "All accounts already exist"
        else result.imported === 0 (metadata refresh)
            BackupRestoreManager->>User: restoreBackupRefreshSuccess
        else new accounts imported
            BackupRestoreManager->>User: restoreBackupSuccess
        end

        BackupRestoreManager->>CodexCLI: autoSyncActiveAccountToCodex()
    end
Loading

Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: lib/storage.ts
Line: 856-861

Comment:
**`EBUSY` and `ENOTEMPTY` are retried on all platforms**

`EPERM` is correctly scoped to `win32`, but `EBUSY` and `ENOTEMPTY` are retried everywhere. on linux/mac, `EBUSY` from `readFile` or `readdir` typically signals a real, non-transient error (mounted device, loop device busy) rather than an AV/write-lock race. silently retrying will add up to ~2 seconds of hidden delay before the real error surfaces. `ENOTEMPTY` also won't appear from `readFile`/`stat`/`readdir`, so it's unreachable in `retryTransientFilesystemOperation` — fine to leave, but worth a comment.

consider scoping `EBUSY` to `win32` too, consistent with `EPERM`:

```suggestion
function isRetryableFilesystemErrorCode(
	code: string | undefined,
): code is "EPERM" | "EBUSY" | "EAGAIN" | "ENOTEMPTY" {
	if (code === "EAGAIN") {
		return true;
	}
	if (code === "ENOTEMPTY") {
		return true;
	}
	return (
		(code === "EBUSY" || code === "EPERM") && process.platform === "win32"
	);
}
```

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

---

This is a comment left during a code review.
Path: test/storage.test.ts
Line: 3786-3829

Comment:
**retry exhaustion path has no vitest coverage**

the only retry test here exercises a single `EAGAIN` failure that succeeds on the second attempt. the PR description calls out "retry exhaustion" as a regression target, but there is no test that drives `TRANSIENT_FILESYSTEM_MAX_ATTEMPTS` (7) consecutive failures and verifies the error propagates instead of looping forever. if the termination condition in `retryTransientFilesystemOperation` regresses (e.g. `>=` flips to `>`), this gap would let an infinite retry loop ship undetected. add a test that fails all 7 attempts and asserts the error is re-thrown on the final attempt.

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

---

This is a comment left during a code review.
Path: lib/storage.ts
Line: 1155-1172

Comment:
**`assertNamedBackupRestorePath` returns the pre-realpath path**

the containment check computes `containedPath = resolvePathForNamedBackupContainment(resolvedPath)` (symlink-resolved) and validates it against `resolvedBackupRoot`, but then returns `resolvedPath` (the pre-realpath value). for any caller that receives this return value and subsequently reads the file, there is a narrow TOCTOU window: a symlink could be swapped in after the `realpathSync` call but before the actual `fs.readFile` in `importAccounts`.

in practice `listNamedBackups` and `findExistingNamedBackupPath` already filter out symlinks, so the risk is contained. but returning `containedPath` (the fully-resolved path) would make `importAccounts` read the exact file that was validated, removing the race entirely:

```suggestion
	return containedPath;
```

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

Last reviewed commit: 94c18b8

Context used:

  • Rule used - What: Every code change must explain how it defend... (source)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

this pr adds named-backup restore support: new storage APIs for create/list/assess/restore, cli/ui wiring for a restore-backup login mode, a backup-restore manager in lib/codex-manager.ts, and broad tests exercising the feature.

Changes

Cohort / File(s) Summary
Documentation
docs/reference/storage-paths.md
added canonical and project-scoped named backup paths and a restore workflow entry.
CLI
lib/cli.ts
added "restore-backup" to LoginMode and mapped fallback keys (u, backup, restore) to it (lib/cli.ts:line ~1).
Storage layer
lib/storage.ts
introduced named-backup public APIs/types (NamedBackupMetadata, BackupRestoreAssessment, ImportAccountsResult, NAMED_BACKUP_LIST_CONCURRENCY, NAMED_BACKUP_ASSESS_CONCURRENCY, listNamedBackups(), getNamedBackupsDirectoryPath(), createNamedBackup(), assessNamedBackupRestore(), restoreNamedBackup()), concurrency-aware listing/assessment, retry helpers, and propagated storagePath through transactions (lib/storage.ts:line ~1-520).
Manager / orchestration
lib/codex-manager.ts
added BackupMenuAction type and runBackupRestoreManager(displaySettings); manager lists backups, batches assessments (concurrency-limited), re-assesses on selection, prompts confirm, and performs restore; integrated into auth/login flow and refactored auth loop (lib/codex-manager.ts:line ~1-500).
UI / copy
lib/ui/auth-menu.ts, lib/ui/copy.ts
added restore-backup action and menu item under a recovery section; added copy entries and confirmation/success messages for restore flows (lib/ui/auth-menu.ts:line ~1, lib/ui/copy.ts:line ~1).
Tests
test/codex-manager-cli.test.ts, test/storage.test.ts, test/cli.test.ts
added extensive tests and mocks for listing/assessing/restoring named backups, confirmation prompt, concurrency and re-assessment behavior, fallback mapping for login prompts, and many edge cases (test/codex-manager-cli.test.ts:line ~1-1200, test/storage.test.ts:line ~1-1300, test/cli.test.ts:line ~1-20).

Sequence diagram

sequenceDiagram
    participant user as User
    participant auth as Auth Menu
    participant mgr as BackupRestoreManager
    participant storage as Storage
    participant fs as File System

    rect rgba(200,200,255,0.5)
    user->>auth: select "restore from backup"
    auth->>mgr: runBackupRestoreManager()
    end

    rect rgba(200,255,200,0.5)
    mgr->>storage: listNamedBackups()
    storage->>fs: read backups dir
    fs-->>storage: backup entries
    storage-->>mgr: NamedBackupMetadata[]
    end

    rect rgba(255,230,200,0.5)
    mgr->>storage: assessNamedBackupRestore(name) (batched)
    storage->>fs: read backup file(s)
    fs-->>storage: backup content
    storage-->>mgr: BackupRestoreAssessment[]
    mgr->>user: display menu with hints
    user->>mgr: choose backup
    mgr->>storage: reassessNamedBackupRestore(name)
    mgr->>user: prompt confirm()
    user->>mgr: confirm
    mgr->>storage: restoreNamedBackup(name)
    storage->>fs: merge/import accounts
    fs-->>storage: write results
    storage-->>mgr: {imported, skipped, total}
    mgr->>user: show result / error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

reviewable concerns

  • missing regression tests: no explicit tests for corrupted backup files encountered mid-restore or concurrent restores of the same named backup. add tests covering partial corruption during restore and concurrent restore attempts (lib/storage.ts:line ~300, test/storage.test.ts:line ~400).
  • concurrency risk: filesystem read/merge/write paths lack cross-process locking; concurrent restores or concurrent writes can race and corrupt data. consider file locks or atomic-replace strategies (lib/storage.ts:line ~300, lib/codex-manager.ts:line ~420).
  • windows path escaping: path resolution and candidate lookup need explicit windows path normalization and traversal protection; add windows-style path tests to validate findExistingNamedBackupPath and resolve helpers (lib/storage.ts:line ~120, test/storage.test.ts:line ~220).
  • ui confirm robustness: confirm() is mocked in tests but may block or fail in non-tty contexts; add tests for confirm timeouts, non-interactive environments, and ensure fallback behavior (lib/codex-manager.ts:line ~340, test/codex-manager-cli.test.ts:line ~50).
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.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 The title follows conventional commits format (feat scope: summary) and is 38 characters, well under 72 char limit. clearly references the main change: adding a backup restore manager to auth flows.
Description check ✅ Passed The PR description is comprehensive, covering summary, motivation, docs/governance updates, validation steps, risk assessment, and rollback plan. All required sections are present.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch git-plan/02-backup-restore-manager
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

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

Choose a reason for hiding this comment

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

3 issues found across 8 files

Prompt for AI agents (unresolved issues)

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


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

<violation number="1" location="test/storage.test.ts:316">
P2: Use retry-based cleanup instead of bare `fs.rm` in test teardown to avoid Windows EBUSY/EPERM/ENOTEMPTY flakiness.</violation>
</file>

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

<violation number="1" location="lib/storage.ts:475">
P2: Backup name normalization in path resolution breaks restore for listed backup files whose filenames contain spaces or other non-normalized characters.</violation>
</file>

<file name="test/codex-manager-cli.test.ts">

<violation number="1" location="test/codex-manager-cli.test.ts:1609">
P3: `vi.doMock` here is never unmocked. `vi.resetModules()` does not clear the mock registry, so this confirm mock will leak into later tests and can change their behavior. Add a `vi.doUnmock`/`vi.unmock` cleanup (e.g., in `afterEach`) to keep tests isolated.</violation>
</file>

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

Copy link

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

Choose a reason for hiding this comment

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

4 issues found across 8 files

Prompt for AI agents (unresolved issues)

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


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

<violation number="1" location="test/storage.test.ts:132">
P2: Replace the bare `fs.rm` in test cleanup with retry/backoff to avoid Windows CI flakes on transient file locks.</violation>
</file>

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

<violation number="1" location="lib/storage.ts:476">
P2: `resolveNamedBackupPath` allows path traversal via unsanitized backup names before directory confinement.</violation>
</file>

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

<violation number="1" location="lib/cli.ts:283">
P2: `restore-backup` is wired only in the TUI path; the fallback login prompt cannot select this new mode.</violation>
</file>

<file name="lib/codex-manager.ts">

<violation number="1" location="lib/codex-manager.ts:4171">
P2: Handle restoreNamedBackup errors so a missing/invalid backup or limit breach doesn’t crash the auth menu.</violation>
</file>

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
lib/cli.ts (1)

49-61: ⚠️ Potential issue | 🟠 Major

the new mode is still unreachable in the readline fallback menu.

this only wires restore-backup through the tty path. promptLoginModeFallback() in lib/cli.ts:174-243 and its prompt copy in lib/ui/copy.ts:135-137 still never advertise or parse the mode, so users in fallback environments cannot reach the restore flow even though runAuthLogin() handles it at lib/codex-manager.ts:3909-3919. please add a vitest for the fallback branch.

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.

Also applies to: 291-292

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

In `@lib/cli.ts` around lines 49 - 61, The fallback readline menu never exposes or
parses the new "restore-backup" LoginMode, so add it to
promptLoginModeFallback() (ensure the option label and value include
"restore-backup") and update the fallback prompt text in lib/ui/copy.ts to
advertise the restore flow (matching how runAuthLogin() in lib/codex-manager.ts
handles "restore-backup"); add a vitest that exercises the fallback branch to
assert the prompt lists and correctly parses "restore-backup", and update any
affected tests to reference the new behavior; while modifying prompts/tests,
verify any updated auth rotation or queue logic touched by this change properly
retries or surfaces EBUSY/429 errors per existing concurrency and Windows IO
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/reference/storage-paths.md`:
- Line 25: Update docs/reference/storage-paths.md to document the restore entry
point and user workflow for the new backup paths: add a short step-by-step
showing "codex auth login" → select recovery → choose 'restore from backup' and
point readers to the interactive restore implementation in lib/codex-manager.ts
(see the interactive flow around the blocks at ~3909-3919 and the restore logic
at ~4211-4291) so users know how to consume files under
~/.codex/multi-auth/backups/<name>.json; also add or link an upgrade note that
mentions the changed CLI workflow and any new npm scripts introduced so README,
SECURITY, and docs remain consistent with the shipped flags and behavior.

In `@lib/codex-manager.ts`:
- Around line 3909-3920: The recovery menu (restore-backup) is never reachable
because runAuthLogin() only enters the menu loop when
existingStorage.accounts.length > 0; hoist the recovery entry so
runBackupRestoreManager(...) can be invoked even when existingStorage.accounts
is empty: move the restore-backup handling (call to runBackupRestoreManager and
its try/catch) to execute before the non-empty accounts guard in runAuthLogin,
ensuring the menu can present the recovery path on fresh/reset/missing storage;
add a vitest that simulates zero-account storage and asserts the restore flow is
offered and that runBackupRestoreManager is called; update any affected tests
referenced in the auth/login suite and ensure new queue logic (if added) handles
EBUSY and 429 retries per concurrency guidelines.

In `@lib/storage.ts`:
- Around line 1712-1728: loadBackupCandidate currently converts any read error
into a permanent invalid-backup result; change it to detect transient Windows/IO
errors (EPERM, EBUSY, EAGAIN) and retry the read (call to loadAccountsFromPath)
with an exponential/backoff loop (same retry envelope used elsewhere in this
module) before returning an error, and apply the same retry logic to the paired
fs.stat call in this file that checks backup file metadata; add a vitest that
simulates a transient busy/EPERM/EBUSY/EAGAIN failure on the first N attempts
and succeeds thereafter to assert the retry path is exercised and cite/update
the affected tests.
- Around line 1580-1609: The function listNamedBackups currently swallows all
readdir errors except ENOENT by logging and returning an empty array; change the
catch in listNamedBackups so that only ENOENT returns [] while any other error
is re-thrown (preserve the original error object) so callers like
runBackupRestoreManager surface real filesystem errors (EPERM/EBUSY). Update
tests: add a vitest that simulates an unreadable backup directory (make readdir
throw EPERM/EBUSY) and assert that listNamedBackups rejects/throws rather than
returning an empty array. Keep references to getNamedBackupRoot/getStoragePath
for locating the path and ensure loadBackupCandidate/buildNamedBackupMetadata
logic is untouched.

In `@test/codex-manager-cli.test.ts`:
- Around line 2365-2428: Add a regression test that covers the confirm=false
path so restore is blocked: duplicate the existing "restores a named backup..."
test setup (use setInteractiveTTY, mock loadAccountsMock, listNamedBackupsMock,
assessNamedBackupRestoreMock, promptLoginModeMock and selectMock) but make
confirmMock resolve to false and then call
runCodexMultiAuthCli(["auth","login"]); assert exitCode is non-zero (or
indicates cancellation) and that restoreNamedBackupMock was not called; keep
other expectations similar (list/assess called once, select used) to ensure the
safety gate implemented by confirmMock is exercised.

In `@test/storage.test.ts`:
- Around line 1111-1185: The tests cover happy paths but lack regression cases
that ensure restoreNamedBackup and assessNamedBackupRestore properly surface
failures for invalid or missing backup payloads; add deterministic vitest tests
in test/storage.test.ts that (1) create a named backup file with invalid JSON
and assert assessNamedBackupRestore(...) and restoreNamedBackup(...) both
reject/throw, (2) create a backup JSON missing required fields (e.g., no
"accounts") and assert both functions reject with a clear error, and (3) assert
behavior is consistent when a backup file is empty or truncated; use the same
helper functions (createNamedBackup, listNamedBackups, assessNamedBackupRestore,
restoreNamedBackup, clearAccounts, loadAccounts) and filesystem writes to inject
the malformed payloads so the tests reproduce the error-path regressions
deterministically under vitest.

---

Outside diff comments:
In `@lib/cli.ts`:
- Around line 49-61: The fallback readline menu never exposes or parses the new
"restore-backup" LoginMode, so add it to promptLoginModeFallback() (ensure the
option label and value include "restore-backup") and update the fallback prompt
text in lib/ui/copy.ts to advertise the restore flow (matching how
runAuthLogin() in lib/codex-manager.ts handles "restore-backup"); add a vitest
that exercises the fallback branch to assert the prompt lists and correctly
parses "restore-backup", and update any affected tests to reference the new
behavior; while modifying prompts/tests, verify any updated auth rotation or
queue logic touched by this change properly retries or surfaces EBUSY/429 errors
per existing concurrency and Windows IO guidelines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f8fa6ad5-4c6c-4110-af7b-d6c8d1c2f62c

📥 Commits

Reviewing files that changed from the base of the PR and between d67ca3d and 7443074.

📒 Files selected for processing (8)
  • docs/reference/storage-paths.md
  • lib/cli.ts
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/ui/auth-menu.ts
  • lib/ui/copy.ts
  • test/codex-manager-cli.test.ts
  • test/storage.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
lib/**

⚙️ CodeRabbit configuration file

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

Files:

  • lib/cli.ts
  • lib/ui/auth-menu.ts
  • lib/codex-manager.ts
  • lib/ui/copy.ts
  • lib/storage.ts
docs/**

⚙️ CodeRabbit configuration file

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

Files:

  • docs/reference/storage-paths.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.test.ts
  • test/codex-manager-cli.test.ts
🔇 Additional comments (3)
test/storage.test.ts (1)

333-336: good windows-safe teardown change.

line 335 in test/storage.test.ts:335 uses removeWithRetry, which reduces eperm/ebusy teardown flakes on windows.

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

506-539: good deterministic mock reset/default setup.

line 506 through line 539 in test/codex-manager-cli.test.ts correctly resets and reseeds new restore-related mocks, which prevents cross-test state bleed.


2430-2500: good restore-failure recovery coverage.

line 2430 through line 2500 in test/codex-manager-cli.test.ts verifies restore failure is surfaced and the login flow continues, which is the right user-safety behavior.

Copy link

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

Choose a reason for hiding this comment

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

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

Prompt for AI agents (unresolved issues)

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


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

<violation number="1" location="test/storage.test.ts:1208">
P3: Duplicate test case: this restore-after-deletion scenario is already covered earlier in the same file.</violation>
</file>

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

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@coderabbitai full review

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@greptileai

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

✅ Actions performed

Full review triggered.

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@cubic-dev-ai review this PR

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 14, 2026

@cubic-dev-ai review this PR

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

Copy link

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

Choose a reason for hiding this comment

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

2 issues found across 9 files

Prompt for AI agents (unresolved issues)

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


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

<violation number="1" location="test/storage.test.ts:1351">
P2: Use `removeWithRetry` instead of bare `fs.rm` to avoid transient Windows file-lock failures in tests.</violation>
</file>

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

<violation number="1" location="lib/storage.ts:1807">
P2: Do not swallow non-ENOENT errors when scanning named backups; rethrow them so restore reports the real filesystem failure instead of a misleading “file not found.”</violation>
</file>

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

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@greptileai

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@cubic-dev-ai review this PR

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@coderabbitai full review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 14, 2026

@cubic-dev-ai review this PR

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

✅ Actions performed

Full review triggered.

Copy link

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

Choose a reason for hiding this comment

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

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

Prompt for AI agents (unresolved issues)

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


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

<violation number="1" location="test/storage.test.ts:2066">
P2: `process.platform` is mocked outside the guarded `try/finally`, so setup failures can leak the win32 mock into later tests.</violation>
</file>

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

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
lib/storage.ts (1)

2867-2873: ⚠️ Potential issue | 🟠 Major

the final restore read still skips the transient-fs retry envelope.

listing and assessment now retry transient EPERM/EBUSY/EAGAIN failures in lib/storage.ts:1606-1704 and lib/storage.ts:1869-1890, but the actual import here still does a one-shot fs.readFile(). that means a backup can pass the menu and then fail during the final restore under the same windows lock conditions. wrap this read in retryTransientFilesystemOperation() and add a vitest that forces one busy read during import.

patch sketch
-	const content = await fs.readFile(resolvedPath, "utf-8");
+	const content = await retryTransientFilesystemOperation(() =>
+		fs.readFile(resolvedPath, "utf-8"),
+	);

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.

♻️ Duplicate comments (1)
lib/storage.ts (1)

2819-2827: ⚠️ Potential issue | 🟠 Major

normalize both storage paths before comparing them.

this guard still does a raw string comparison between transactionState.storagePath and getStoragePath(). on windows, the same location can come through with different casing or normalization, so exportNamedBackupFile() in lib/named-backup-export.ts:223-250 can now fail with a false "different storage path" error from the same logical root.

patch sketch
 	const transactionState = transactionSnapshotContext.getStore();
-	const currentStoragePath = getStoragePath();
+	const normalizeStoragePath = (value: string): string => {
+		const resolved = resolvePath(value);
+		return process.platform === "win32" ? resolved.toLowerCase() : resolved;
+	};
+	const currentStoragePath = normalizeStoragePath(getStoragePath());
 	const storage = transactionState?.active
-		? transactionState.storagePath === currentStoragePath
+		? normalizeStoragePath(transactionState.storagePath) === currentStoragePath
 			? transactionState.snapshot
 			: (() => {

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 2819 - 2827, The comparison between
transactionState.storagePath and getStoragePath() in the exportAccounts path
does a raw string compare and can fail on Windows due to differing
casing/normalization; update the guard in the exportAccounts logic to normalize
both sides before comparing (e.g., use a shared normalize/resolve approach such
as path.resolve + path.normalize or fs.realpathSync where appropriate) so
transactionState.storagePath and getStoragePath() are compared canonicalized,
and ensure exportNamedBackupFile() still receives the original intended path
after the check; modify the code around transactionState.storagePath,
getStoragePath(), and the exportAccounts guard to use that normalizer function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/cli.ts`:
- Around line 237-244: Add test assertions to cover the remaining fallback
aliases so the CLI mapping is fully exercised: in the existing CLI unit tests
that already check "u" and "restore" (near the current test block around the
other alias assertions), add cases asserting that passing "backup" and
"restore-backup" also produce { mode: "restore-backup" }. Target the same test
helper that parses CLI input (the test invoking the code path that reaches the
normalized checks in lib/cli.ts) so both "backup" and "restore-backup" are
explicitly asserted to return mode "restore-backup".

In `@lib/codex-manager.ts`:
- Around line 4353-4375: The restore path that calls
importAccounts(latestAssessment.backup.path) updates multi-auth storage but does
not push the restored active account into the codex CLI state; after detecting a
changed restore (i.e., result.changed is true and after you log the success
message), call autoSyncActiveAccountToCodex() to sync the active account back
into the codex CLI state (referencing importAccounts and
autoSyncActiveAccountToCodex in lib/codex-manager.ts); also add/extend the
restore vitest to cover the changed-restore case so the zero-account recovery
path no longer leaves downstream auth empty, and ensure any added logs do not
leak tokens or emails and that any new queue logic respects EBUSY/429 retry
behavior per project guidelines.

In `@lib/ui/copy.ts`:
- Around line 19-37: The UI copy for restoreBackupConfirm and
restoreBackupRefreshSuccess misleadingly shows exact numeric "refreshed" counts
while the restore flow does not compute updated-row counts—lib/storage.ts
currently computes `skipped` as every non-imported row and lib/codex-manager.ts
feeds that into the "refreshed" message; fix by changing the restore flow to
return an explicit updated/refreshed count from the storage layer (e.g., add an
`updated` or `refreshed` field to the result returned by the storage method used
in lib/storage.ts and use that in lib/codex-manager.ts) and then wire that
accurate count into restoreBackupRefreshSuccess and the zero-import branch used
by restoreBackupConfirm, or alternatively change those two copy strings to
non-numeric wording (e.g., "refreshed existing accounts" without a number) if
you cannot compute an exact count; also add a vitest regression that covers a
mixed case (some duplicates updated, some unchanged) to ensure the UI shows the
correct semantics moving forward.

In `@test/cli.test.ts`:
- Around line 707-719: The test should assert that the other two accepted
aliases also map to mode "restore-backup": update the "returns restore-backup
for fallback restore aliases" case that imports promptLoginMode to also call
mockRl.question.mockResolvedValueOnce("backup") and expect(promptLoginMode([{
index: 0 }])).resolves.toEqual({ mode: "restore-backup" }), and likewise
mockRl.question.mockResolvedValueOnce("restore-backup") with the same
expectation; keep using the existing mockRl.question flow and promptLoginMode
function to lock the full alias mapping.

In `@test/storage.test.ts`:
- Around line 454-466: The test's broad assertion
expect(writeFileSpy).not.toHaveBeenCalled() can fail due to unrelated fs writes;
instead scope assertions to the active storage target by checking
writeFileSpy.mock.calls (or using
expect(writeFileSpy).not.toHaveBeenCalledWith(...)) for the specific path(s)
used by importAccounts/exportPath (and the other case around lines 539–551).
Replace the global not-to-have-been-called with an assertion that no call's
first argument equals or contains exportPath (or the exact storage filename(s)
used by the storage layer), e.g. iterate writeFileSpy.mock.calls and assert none
reference the active storage path(s) so only writes to the target files cause
failures.
- Around line 2048-2081: The test currently only asserts that
restoreNamedBackup("Manual Backup") throws after the backup file is removed;
also assert that the active storage state remained unchanged to guarantee
atomicity—after the expect(...rejects.toThrow(...)) add assertions that check
the same state you had before restore was attempted (e.g., call whatever reads
active accounts/storage used elsewhere in this file such as
clearAccounts/readActiveAccounts/getAccounts or inspect testStoragePath
contents) to ensure no accounts were added/modified; use the existing helpers in
this test file (clearAccounts, assessNamedBackupRestore, restoreNamedBackup,
removeWithRetry) to capture pre-restore state, then re-read and assert equality
after the failed restore so the manual-backup deletion path is deterministic and
verifies restore atomicity.

---

Duplicate comments:
In `@lib/storage.ts`:
- Around line 2819-2827: The comparison between transactionState.storagePath and
getStoragePath() in the exportAccounts path does a raw string compare and can
fail on Windows due to differing casing/normalization; update the guard in the
exportAccounts logic to normalize both sides before comparing (e.g., use a
shared normalize/resolve approach such as path.resolve + path.normalize or
fs.realpathSync where appropriate) so transactionState.storagePath and
getStoragePath() are compared canonicalized, and ensure exportNamedBackupFile()
still receives the original intended path after the check; modify the code
around transactionState.storagePath, getStoragePath(), and the exportAccounts
guard to use that normalizer function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5ecccd83-d873-4ff5-9403-ab6206faf358

📥 Commits

Reviewing files that changed from the base of the PR and between d67ca3d and 082b061.

📒 Files selected for processing (9)
  • docs/reference/storage-paths.md
  • lib/cli.ts
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/ui/auth-menu.ts
  • lib/ui/copy.ts
  • test/cli.test.ts
  • test/codex-manager-cli.test.ts
  • test/storage.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
lib/**

⚙️ CodeRabbit configuration file

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

Files:

  • lib/cli.ts
  • lib/ui/copy.ts
  • lib/ui/auth-menu.ts
  • lib/codex-manager.ts
  • lib/storage.ts
docs/**

⚙️ CodeRabbit configuration file

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

Files:

  • docs/reference/storage-paths.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/cli.test.ts
  • test/storage.test.ts
  • test/codex-manager-cli.test.ts
🔇 Additional comments (19)
lib/cli.ts (2)

49-61: lgtm, new login mode variant added correctly.

the restore-backup mode variant integrates cleanly with the existing union. downstream handling in lib/codex-manager.ts wires this to runBackupRestoreManager.


299-300: tty path wiring looks correct.

the case "restore-backup" delegates cleanly; corresponding ui action type is introduced in lib/ui/auth-menu.ts. no token or email logging here.

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

9-12: mock wiring for named backup apis looks correct.

test/codex-manager-cli.test.ts:9-12 declares mocks for listNamedBackups, assessNamedBackupRestore, getNamedBackupsDirectoryPath, and importAccounts. these are properly wired into the storage mock at lines 110-113. the setup aligns with the public api surface in lib/storage.ts.

Also applies to: 110-113


197-201: confirm mock setup is clean.

test/codex-manager-cli.test.ts:197-201 introduces confirmMock for the ui confirmation dialog. this enables testing the confirm-decline path at line 2523 and the confirm-proceed path throughout the restore tests.


506-540: beforeeach reset for backup mocks covers defaults well.

test/codex-manager-cli.test.ts:506-540 resets all new mocks and provides sensible defaults. the default assessNamedBackupRestoreMock returns a valid, eligible assessment with eligibleForRestore: true. this prevents test pollution across cases.


2366-2431: restore happy path test is solid.

test/codex-manager-cli.test.ts:2366-2431 validates the full restore flow: list → assess → select → confirm → import. assertions verify listNamedBackupsMock, assessNamedBackupRestoreMock, confirmMock, and importAccountsMock interactions. the backup path is correctly passed through.


2480-2545: confirm-decline regression is now complete.

test/codex-manager-cli.test.ts:2480-2545 addresses the past review comment. it sets confirmMock.mockResolvedValueOnce(false) at line 2523 and asserts both that importAccountsMock is not called and that promptLoginModeMock.toHaveBeenCalledTimes(2) at line 2543, proving the flow returns to the menu.


2547-2620: restore failure regression proves menu loop resumes.

test/codex-manager-cli.test.ts:2547-2620 addresses the past review comment by asserting promptLoginModeMock.toHaveBeenCalledTimes(2) at line 2605. this locks in the "stay in menu flow" contract when importAccountsMock rejects.


2696-2726: eperm regression for backup listing is present.

test/codex-manager-cli.test.ts:2696-2726 covers the EPERM error code from listNamedBackupsMock and verifies the cli returns to the login menu. this addresses the past review comment about manager-level EPERM regression.


2796-2871: concurrency bounding test is well-designed.

test/codex-manager-cli.test.ts:2796-2871 imports NAMED_BACKUP_ASSESS_CONCURRENCY from the actual storage module, creates more backups than the limit, and tracks maxInFlight using deferred promises. the assertion at line 2868 confirms bounded concurrency is enforced. deterministic and covers the concurrency concern.


3138-3224: ebusy reassessment rejection regression is present.

test/codex-manager-cli.test.ts:3138-3224 addresses the past review comment. the second assessNamedBackupRestoreMock call rejects with EBUSY at line 3182, and the test asserts confirmMock and importAccountsMock are not called, with the error logged.


3226-3264: epoch timestamp hint test avoids locale brittleness.

test/codex-manager-cli.test.ts:3226-3264 addresses the past review comment about locale-specific date strings. line 3263 now asserts not.toContain("updated ") for updatedAt: 0, avoiding the hardcoded locale-dependent date.


3266-3358: recent timestamp formatting test uses date.now spy.

test/codex-manager-cli.test.ts:3266-3358 spies on Date.now at line 3270 to control the "now" reference, then asserts relative hints like "updated today", "updated yesterday", "updated 3d ago". the spy is restored in the finally block. deterministic.


3537-3657: quick check waits for in-flight quota refresh.

test/codex-manager-cli.test.ts:3537-3657 uses deferred promises to control fetch ordering and verifies the second fetch occurs after the first completes. the test ensures quota cache is populated for both accounts before the check proceeds. this covers a potential race condition.


2728-2794: partial assessment failure test is comprehensive.

test/codex-manager-cli.test.ts:2728-2794 simulates one backup throwing during assessment while another succeeds. the selectMock implementation at line 2770 verifies only healthy backups appear in the menu. the warning is asserted at line 2786.


2873-2959: reassessment freshness test validates toctou guard.

test/codex-manager-cli.test.ts:2873-2959 ensures assessNamedBackupRestoreMock is called twice (initial + pre-confirm) and that the confirmation prompt uses the refreshed assessment data. this locks in the toctou re-assessment behavior.


3041-3136: reassessment ineligibility test proves early exit.

test/codex-manager-cli.test.ts:3041-3136 covers the case where reassessment returns eligibleForRestore: false. the test confirms confirmMock and importAccountsMock are not called, and the appropriate message is logged.

test/storage.test.ts (2)

373-399: good regression for cross-path transaction guard

test/storage.test.ts:373 is a strong guardrail for the storage-path transaction boundary and catches a high-risk misuse path early.


1857-2365: solid windows retry and bounded-concurrency coverage

the regressions in test/storage.test.ts:1857 through test/storage.test.ts:2365 do a good job pinning transient fs retry behavior and NAMED_BACKUP_LIST_CONCURRENCY throttling.

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@greptileai

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@coderabbitai full review
@cubic-dev-ai review this PR
@greptileai

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

@ndycode — triggering a full review of pr #76 now. scope will cover:

  • lib/storage.ts — triple-negation in findExistingNamedBackupPath, swallowed validation error in resolveNamedBackupRestorePath catch block (replaces real errors with "import file not found"), NAMED_BACKUP_LIST_CONCURRENCY bounding, symlink rejection, toctou re-assessment guards, retryTransientFilesystemOperation eperm/ebusy/eagain coverage, and the storagePath transaction guard in exportAccounts
  • lib/codex-manager.tsrunBackupRestoreManager fan-out concurrency, re-assessment-before-confirm flow, error containment back to login menu, formatRelativeDateShort edge cases (midnight rollover, locale), and the refactored runAuthLogin loop
  • lib/cli.tsrestore-backup mode variant and fallback alias wiring for "u"/"backup"/"restore"
  • lib/ui/auth-menu.ts / lib/ui/copy.ts — recovery section ui injection and copy string additions
  • test/storage.test.ts / test/codex-manager-cli.test.ts — concurrency regression, windows eperm retry, uppercase .JSON restore, toctou delete-after-assessment, partial assessment failure, symlink rejection, re-assessment freshness, and zero-account recovery paths
✅ Actions performed

Full review triggered.

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 14, 2026

@coderabbitai full review
@cubic-dev-ai review this PR
@greptileai

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

Copy link

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

Choose a reason for hiding this comment

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

1 issue found across 9 files

Prompt for AI agents (unresolved issues)

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


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

<violation number="1" location="lib/storage.ts:2017">
P1: Restore path containment is lexical-only and can be bypassed through symlinked backup roots.</violation>
</file>

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

@ndycode
Copy link
Owner Author

ndycode commented Mar 15, 2026

@greptileai
@cubic-dev-ai review this PR

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 15, 2026

@greptileai
@cubic-dev-ai review this PR

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

Copy link

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

Choose a reason for hiding this comment

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

No issues found across 9 files

@ndycode
Copy link
Owner Author

ndycode commented Mar 15, 2026

@coderabbitai full review
@cubic-dev-ai review this PR
@greptileai

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 15, 2026

@coderabbitai full review
@cubic-dev-ai review this PR
@greptileai

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

@ndycode — triggering a full review of pr #76 now. scope will cover:

  • lib/storage.ts — triple-negation in findExistingNamedBackupPath, swallowed validation error in resolveNamedBackupRestorePath catch block (replaces real errors with "import file not found"), NAMED_BACKUP_LIST_CONCURRENCY bounding, symlink rejection, toctou re-assessment guards, retryTransientFilesystemOperation eperm/ebusy/eagain coverage, and the storagePath transaction guard in exportAccounts
  • lib/codex-manager.tsrunBackupRestoreManager fan-out concurrency, re-assessment-before-confirm flow, error containment back to login menu, formatRelativeDateShort edge cases (midnight rollover, locale), and the refactored runAuthLogin loop
  • lib/cli.tsrestore-backup mode variant and fallback alias wiring for "u"/"backup"/"restore"
  • lib/ui/auth-menu.ts / lib/ui/copy.ts — recovery section ui injection and copy string additions
  • test/storage.test.ts / test/codex-manager-cli.test.ts — concurrency regression, windows eperm retry, uppercase .JSON restore, toctou delete-after-assessment, partial assessment failure, symlink rejection, re-assessment freshness, and zero-account recovery paths
✅ Actions performed

Full review triggered.

Copy link

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

Choose a reason for hiding this comment

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

2 issues found across 9 files

Prompt for AI agents (unresolved issues)

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


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

<violation number="1" location="test/storage.test.ts:2255">
P3: Avoid hardcoding `chunk-boundary-08`; derive the expected backup name from `NAMED_BACKUP_LIST_CONCURRENCY` so the test remains valid if concurrency changes.</violation>
</file>

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

<violation number="1" location="lib/storage.ts:2029">
P2: Reject symlinked backup roots in restore path validation. The current containment check follows `backupRoot` symlinks, so a symlinked backups directory can redirect restores outside the intended backups boundary.</violation>
</file>

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

Comment on lines +2029 to +2031
const resolvedBackupRoot = resolvePathForNamedBackupContainment(backupRoot);
const containedPath = resolvePathForNamedBackupContainment(resolvedPath);
const relativePath = relative(resolvedBackupRoot, containedPath);
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: Reject symlinked backup roots in restore path validation. The current containment check follows backupRoot symlinks, so a symlinked backups directory can redirect restores outside the intended backups boundary.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/storage.ts, line 2029:

<comment>Reject symlinked backup roots in restore path validation. The current containment check follows `backupRoot` symlinks, so a symlinked backups directory can redirect restores outside the intended backups boundary.</comment>

<file context>
@@ -1574,6 +1922,165 @@ async function loadAccountsFromPath(path: string): Promise<{
+	backupRoot: string,
+): string {
+	const resolvedPath = resolvePath(path);
+	const resolvedBackupRoot = resolvePathForNamedBackupContainment(backupRoot);
+	const containedPath = resolvePathForNamedBackupContainment(resolvedPath);
+	const relativePath = relative(resolvedBackupRoot, containedPath);
</file context>
Suggested change
const resolvedBackupRoot = resolvePathForNamedBackupContainment(backupRoot);
const containedPath = resolvePathForNamedBackupContainment(resolvedPath);
const relativePath = relative(resolvedBackupRoot, containedPath);
const resolvedBackupRoot = resolvePath(backupRoot);
const canonicalBackupRoot = resolvePathForNamedBackupContainment(
resolvedBackupRoot,
);
if (
existsSync(resolvedBackupRoot) &&
normalizeStoragePathForComparison(canonicalBackupRoot) !==
normalizeStoragePathForComparison(resolvedBackupRoot)
) {
throw new Error("Backup path escapes backup directory");
}
const containedPath = resolvePathForNamedBackupContainment(resolvedPath);
const relativePath = relative(canonicalBackupRoot, containedPath);
Fix with Cubic

expect(listedBackups).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "chunk-boundary-08",
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

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

P3: Avoid hardcoding chunk-boundary-08; derive the expected backup name from NAMED_BACKUP_LIST_CONCURRENCY so the test remains valid if concurrency changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/storage.test.ts, line 2255:

<comment>Avoid hardcoding `chunk-boundary-08`; derive the expected backup name from `NAMED_BACKUP_LIST_CONCURRENCY` so the test remains valid if concurrency changes.</comment>

<file context>
@@ -1073,6 +1403,1346 @@ describe("storage", () => {
+				expect(listedBackups).toEqual(
+					expect.arrayContaining([
+						expect.objectContaining({
+							name: "chunk-boundary-08",
+							valid: true,
+						}),
</file context>
Suggested change
name: "chunk-boundary-08",
name: `chunk-boundary-${String(NAMED_BACKUP_LIST_CONCURRENCY).padStart(2, "0")}`,
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant