Skip to content

feat(auth): add first-run setup wizard#129

Open
ndycode wants to merge 5 commits intoclean/pr102-opencode-import-replay-finalfrom
clean/pr103-first-run-wizard
Open

feat(auth): add first-run setup wizard#129
ndycode wants to merge 5 commits intoclean/pr102-opencode-import-replay-finalfrom
clean/pr103-first-run-wizard

Conversation

@ndycode
Copy link
Owner

@ndycode ndycode commented Mar 19, 2026

Summary

  • clean replacement for bloated #103
  • narrows the review back to the first-run wizard slice only
  • stacked on clean/pr102-opencode-import-replay-final

What Changed

  • adds the first-run setup wizard before OAuth for brand-new installs, with restore, OpenCode import, settings, and doctor entry points
  • preserves the intended first-run import and restore flows while keeping path output redacted and the sync-history/storage updates limited to this slice
  • fixes successful first-run OpenCode import so codex auth login hands off into the normal account menu instead of exiting immediately after import
  • keeps the replay clean to the intended 9-file feature diff instead of the old 43-file branch

Validation

  • npm run lint
  • npm run typecheck
  • npm run build
  • npm test
  • npm exec -- vitest run test/codex-manager-cli.test.ts test/storage.test.ts test/sync-history.test.ts --maxWorkers=1
  • npm exec -- vitest run test/codex-manager-cli.test.ts test/storage.test.ts test/sync-history.test.ts test/cli-auth-menu.test.ts test/auth-menu-hotkeys.test.ts --maxWorkers=1

Docs and Governance Checklist

  • README updated (if user-visible behavior changed)
  • docs/getting-started.md updated (if onboarding flow changed)
  • docs/features.md updated (if capability surface changed)
  • relevant docs/reference/* pages updated (if commands/settings/paths changed)
  • docs/upgrade.md updated (if migration behavior changed)
  • SECURITY.md and CONTRIBUTING.md reviewed for alignment

Risk and Rollback

  • Risk level: medium
  • Rollback plan: revert fd189fe

Additional Notes

  • Supersedes #103.
  • This replacement diff is 9 files instead of the old 43-file PR.
  • Includes the first-run import handoff fix so successful imports continue into the normal login/dashboard flow.

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 a first-run setup wizard that appears before oauth when no storage file exists, offering restore, opencode import, settings, doctor, and skip/login entry points. it's a clean 9-file slice that correctly fixes the post-import handoff so successful opencode imports flow into the normal account menu instead of exiting.

key changes:

  • runFirstRunWizard / buildFirstRunWizardOptions / showFirstRunWizard form the new wizard stack; skipEmptyStorageRecoveryMenu and firstRunWizardShownInLoop flags thread the result into the existing login loop
  • detectOpencodeAccountPoolPath probes LOCALAPPDATA, APPDATA, and ~/.opencode; assessOpencodeAccountPool wraps it with the existing restore-assessment pipeline; active-storage self-import guard added to both assessOpencodeAccountPool and importAccounts
  • pruneAutoGeneratedSnapshots + enforceSnapshotRetention add per-reason snapshot retention (default 3) called after every snapshotAccountStorage write; dynamic import of sync-history.js breaks the circular dep
  • withRetryableHistoryWrite adds retry on EBUSY/EPERM/EACCES for all history file writes; pruneSyncHistoryEntries is now a pure exported function; estimate-guard (HISTORY_TRIM_RELOAD_BUFFER) reduces trim-read I/O; drain-wait test is restored
  • redactFilesystemDetails regex fixed from + back to * restoring single-segment path redaction; formatRedactedFilesystemError exported for consistent cross-module error formatting

the main open items after previous review threads: buildFirstRunWizardOptions runs three independent async probes sequentially (noticeable delay on windows/AV-heavy hosts — should be Promise.allSettled), and pruneAutoGeneratedSnapshots has no direct vitest test verifying that old snapshots beyond the retention limit are actually unlinked from disk.

Confidence Score: 3/5

  • safe to merge with the dead-code block and sequential-fetch issue tracked; no data-loss or token-leak regressions visible in this diff
  • previous thread issues (single-pass drain, multi-process estimate, dead-code second wizard call, single-segment redaction) are partially or fully addressed in this revision. remaining gaps are the sequential buildFirstRunWizardOptions fetches (perf, not correctness) and missing direct pruning test. the regex is correctly fixed to *. EACCES is now in the retry set. the drain-wait test is restored. no new token-safety or data-mutation regressions introduced.
  • lib/codex-manager.ts (sequential first-run fetches, dead-code inner wizard block), lib/storage.ts (missing pruning test coverage)

Important Files Changed

Filename Overview
lib/codex-manager.ts adds first-run wizard flow, opencode import in both wizard and main login loop, and buildFirstRunWizardOptions with sequential awaits (perf concern); the dead-code block at the inner wizard re-check was flagged in a previous review thread
lib/storage.ts adds detectOpencodeAccountPoolPath, assessOpencodeAccountPool, pruneAutoGeneratedSnapshots, formatRedactedFilesystemError, and snapshot-retention enforcement; regex fix restores single-segment path redaction; new function lacks direct pruning test coverage
lib/sync-history.ts adds withRetryableHistoryWrite (EBUSY/EPERM/EACCES), pruneSyncHistoryEntries, pruneSyncHistory, and estimate-guard to reduce trim-read I/O; ETIMEDOUT omitted from retry set (noted in prior thread); multi-process estimate drift noted in prior thread
test/storage.test.ts adds opencode pool detection suite and active-file guard tests; missing direct test for pruneAutoGeneratedSnapshots success path (retention enforcement)
test/sync-history.test.ts restores the previously-deleted drain-wait test, adds estimate-guard boundary tests, retry tests, and prune-on-disk tests; solid coverage for the new trim/retry logic

Sequence Diagram

sequenceDiagram
    participant CLI as codex auth login
    participant CM as codex-manager
    participant WIZ as runFirstRunWizard
    participant ST as storage.ts
    participant SH as sync-history.ts

    CLI->>CM: runAuthLogin()
    CM->>ST: loadAccounts()
    ST-->>CM: null (no storage)
    CM->>CM: shouldShowFirstRunWizard() → true
    CM->>ST: buildFirstRunWizardOptions()<br/>(listNamedBackups, listRotatingBackups, assessOpencodeAccountPool — sequential)
    ST-->>CM: { namedBackupCount, rotatingBackupCount, hasOpencodeSource }
    CM->>WIZ: showFirstRunWizard(options)

    alt user picks "import-opencode"
        WIZ->>ST: assessOpencodeAccountPool()
        ST-->>WIZ: BackupRestoreAssessment
        WIZ->>ST: importAccounts(path)
        ST->>SH: appendSyncHistoryEntry (via snapshotAccountStorage)
        SH-->>ST: ok
        ST->>ST: enforceSnapshotRetention → pruneAutoGeneratedSnapshots
        ST-->>WIZ: { imported, skipped, total }
        WIZ->>ST: loadAccounts()
        ST-->>WIZ: AccountStorageV3 (accounts > 0)
        WIZ-->>CM: { outcome: "continue", latestStorage, firstAction: "import-opencode" }
        CM->>CM: enter loginFlow loop with populated storage
    else user picks "login" or "skip"
        WIZ-->>CM: { outcome: "continue", latestStorage: null, firstAction: "login"|"skip" }
        CM->>CM: firstRunWizardShownInLoop = true<br/>skipEmptyStorageRecoveryMenu = true
        CM->>CM: enter loginFlow → runOAuthFlow
    else user picks "cancel"
        WIZ-->>CM: { outcome: "cancelled" }
        CM-->>CLI: return 0
    end
Loading

Comments Outside Diff (2)

  1. lib/storage.ts, line 2088-2097 (link)

    P2 relative APPDATA paths no longer resolved — windows edge case

    removing resolveFilesystemPath means that if APPDATA or LOCALAPPDATA contains a relative path (possible in sandboxed environments, CI, or user misconfiguration), the candidate path will be relative too. existsSync on a relative path resolves against process.cwd(), which can differ from where the path was intended. the test "resolves relative app data candidates before detection" was deleted alongside this change, so there is now no coverage for relative env-var scenarios. the change is likely intentional, but it is worth noting that the previous resolve() call was a cheap safety net with no downside.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: lib/storage.ts
    Line: 2088-2097
    
    Comment:
    **relative APPDATA paths no longer resolved — windows edge case**
    
    removing `resolveFilesystemPath` means that if `APPDATA` or `LOCALAPPDATA` contains a relative path (possible in sandboxed environments, CI, or user misconfiguration), the candidate path will be relative too. `existsSync` on a relative path resolves against `process.cwd()`, which can differ from where the path was intended. the test "resolves relative app data candidates before detection" was deleted alongside this change, so there is now no coverage for relative env-var scenarios. the change is likely intentional, but it is worth noting that the previous `resolve()` call was a cheap safety net with no downside.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

  2. lib/storage.ts, line 2401-2407 (link)

    P1 home candidate skips resolvePath while appdata candidates use it

    the appdata candidates now go through resolvePath(join(base, ...)) (with a try/catch for out-of-root paths), but the .opencode home candidate uses a bare join:

    candidates.add(join(home, ".opencode", ACCOUNTS_FILE_NAME));

    homedir() returns an absolute path in normal environments, so in practice this rarely matters. but the inconsistency means: if homedir() returns a relative path (e.g. in a containerised build environment or stripped-down CI runner), this candidate is resolved relative to process.cwd() rather than an absolute root — the same class of bug that resolvePath was added to guard against for appdata.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: lib/storage.ts
    Line: 2401-2407
    
    Comment:
    **`home` candidate skips `resolvePath` while appdata candidates use it**
    
    the appdata candidates now go through `resolvePath(join(base, ...))` (with a try/catch for out-of-root paths), but the `.opencode` home candidate uses a bare `join`:
    
    ```ts
    candidates.add(join(home, ".opencode", ACCOUNTS_FILE_NAME));
    ```
    
    `homedir()` returns an absolute path in normal environments, so in practice this rarely matters. but the inconsistency means: if `homedir()` returns a relative path (e.g. in a containerised build environment or stripped-down CI runner), this candidate is resolved relative to `process.cwd()` rather than an absolute root — the same class of bug that `resolvePath` was added to guard against for appdata.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

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: 4323-4360

Comment:
**sequential async fetches on first-run visible delay**

`buildFirstRunWizardOptions` runs three independent async operations sequentially. on windows with a slow or AV-scanned filesystem — exactly the oc-chatgpt-multi-auth target — this triples the delay before the wizard even appears (each `listNamedBackups`, `listRotatingBackups`, and `assessOpencodeAccountPool` can each block on disk I/O independently). use `Promise.allSettled` to run them in parallel:

```ts
async function buildFirstRunWizardOptions(): Promise<FirstRunWizardOptions> {
	const [namedResult, rotatingResult, opencodeResult] = await Promise.allSettled([
		listNamedBackups(),
		listRotatingBackups(),
		assessOpencodeAccountPool(),
	]);

	const namedBackupCount =
		namedResult.status === "fulfilled" ? namedResult.value.length : 0;
	const rotatingBackupCount =
		rotatingResult.status === "fulfilled" ? rotatingResult.value.length : 0;
	const hasOpencodeSource =
		opencodeResult.status === "fulfilled" && opencodeResult.value !== null;

	if (namedResult.status === "rejected") {
		console.warn(`Failed to list named backups (${getRedactedFilesystemErrorLabel(namedResult.reason)}).`);
	}
	if (rotatingResult.status === "rejected") {
		console.warn(`Failed to list rotating backups (${getRedactedFilesystemErrorLabel(rotatingResult.reason)}).`);
	}
	if (opencodeResult.status === "rejected") {
		console.warn(`Failed to detect OpenCode import source (${getRedactedFilesystemErrorLabel(opencodeResult.reason)}).`);
	}

	return {
		storagePath: getStoragePath(),
		namedBackupCount,
		rotatingBackupCount,
		hasOpencodeSource,
	};
}
```

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: 2399-2455

Comment:
**`pruneAutoGeneratedSnapshots` missing direct vitest coverage**

`pruneAutoGeneratedSnapshots` is now exported and called from `snapshotAccountStorage` on every snapshot write via `enforceSnapshotRetention`. the new test suite in `test/storage.test.ts` covers detection, assessment, and import paths but there is no test that verifies:

- old auto-snapshots beyond `ACCOUNT_SNAPSHOT_RETENTION_PER_REASON` (3) are actually unlinked from disk after a snapshot is written
- the rollback-snapshot preservation (`getLatestManualCodexCliRollbackSnapshotNames`) pins the right name across prune runs
- the error-swallow path (failed `unlinkWithRetry` keeps the name in `keptNames`) reports correctly

without this, a regression in the sorting (`sortTimestamp` comparison, `keepLatestPerReason` clamp) or the rollback-preserve logic would go undetected. at minimum add one integration-style vitest test that calls `snapshotAccountStorage` `ACCOUNT_SNAPSHOT_RETENTION_PER_REASON + 2` times and asserts only `ACCOUNT_SNAPSHOT_RETENTION_PER_REASON` snapshot files remain on disk.

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

Last reviewed commit: "fix(sync-history): r..."

@chatgpt-codex-connector
Copy link

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.

@ndycode ndycode mentioned this pull request Mar 19, 2026
12 tasks
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Warning

Rate limit exceeded

@ndycode has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c6e5902-489a-4f45-9a2d-16a2ea231560

📥 Commits

Reviewing files that changed from the base of the PR and between 3f06c8e and d22d9e1.

📒 Files selected for processing (9)
  • docs/getting-started.md
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/sync-history.ts
  • lib/ui/auth-menu.ts
  • lib/ui/copy.ts
  • test/codex-manager-cli.test.ts
  • test/storage.test.ts
  • test/sync-history.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch clean/pr103-first-run-wizard
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch clean/pr103-first-run-wizard
📝 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.

@ndycode ndycode force-pushed the clean/pr102-opencode-import-replay-final branch from 3f06c8e to e407350 Compare March 19, 2026 15:55
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