Skip to content

feat(auth): harden opencode import flow#128

Closed
ndycode wants to merge 4 commits intoclean/pr101-dashboard-health-summaryfrom
clean/pr102-opencode-import-replay-final
Closed

feat(auth): harden opencode import flow#128
ndycode wants to merge 4 commits intoclean/pr101-dashboard-health-summaryfrom
clean/pr102-opencode-import-replay-final

Conversation

@ndycode
Copy link
Owner

@ndycode ndycode commented Mar 19, 2026

Summary

  • clean replacement for bloated #102
  • narrows the review back to the OpenCode import slice only
  • stacked on clean/pr101-dashboard-health-summary

What Changed

  • rebuilds the OpenCode import flow on top of the cleaned dashboard-health base instead of carrying the old mixed-scope branch history
  • keeps import assessment and execution aligned with current restore contracts, including self-import rejection against the active storage file
  • preserves the safer current restore-preview behavior and redacted-path handling while keeping the login loop non-fatal when import work fails
  • adds coverage for the import/login/storage/doc paths that moved during the replay so the feature work is validated in the current codebase shape

Validation

  • npm run lint
  • npm run typecheck
  • npm run build
  • npm test
  • npm exec -- vitest run test/destructive-actions.test.ts test/quota-cache.test.ts test/storage.test.ts test/storage-flagged.test.ts test/codex-manager-cli.test.ts test/cli.test.ts test/cli-auth-menu.test.ts test/auth-menu-hotkeys.test.ts test/release-main-prs-regression.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 3f06c8e

Additional Notes

  • Supersedes #102.
  • This replacement diff is 9 files instead of the old 43-file PR.

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 up the opencode account import slice cleanly on top of the dashboard-health base — it adds detectOpencodeAccountPoolPath, assessOpencodeAccountPool, and a self-import guard to importAccounts in lib/storage.ts, a new import-opencode branch in the login loop in lib/codex-manager.ts, and a full suite of vitest coverage for detection, assessment, retention pruning, and the manager flow.

key observations:

  • self-import protection is solidequalsResolvedStoragePath handles windows case-folding and both assessOpencodeAccountPool and importAccounts independently enforce the guard; the self-import test now correctly uses mockRejectedValueOnce + errorSpy (addresses the previous review thread)
  • concurrency guard in pruneAutoGeneratedSnapshots — the path-keyed retentionEnforcementInFlight map correctly deduplicates concurrent calls; the concurrent-prune test is thorough with an async unlink gate
  • pruneAutoGeneratedSnapshots default keepLatestPerReason is 1, not ACCOUNT_SNAPSHOT_RETENTION_PER_REASON = 3 — the production path (enforceSnapshotRetention) always passes the constant, so the live retention policy is correct; but any direct caller of the exported function without specifying the option silently applies a more aggressive policy than the exported constant implies (see inline comment)
  • invalid/ineligible pool errors are emitted on console.log, not console.error — minor channel mismatch with the throw and import-failure paths; relevant on windows where stdout/stderr are often captured separately
  • redactFilesystemDetails regex +* change is intentional and tested but benefits from a short inline comment to explain why single-segment paths are now in scope
  • rollback history scan uses continue (not break) for empty snapshot names — all previously flagged issues are addressed

Confidence Score: 4/5

  • safe to merge after fixing the keepLatestPerReason default and reviewing the console channel mismatch for invalid-pool errors
  • all previously flagged issues are addressed; the core import flow, self-import protection, path redaction, and concurrency guard are correct and well-tested; the keepLatestPerReason default inconsistency is a real footgun for future direct callers of the exported function but does not affect the current live retention path; the console channel mismatch is a minor UX issue not a correctness bug
  • lib/storage.ts (keepLatestPerReason default and redact regex comment), lib/codex-manager.ts (console channel for invalid assessment)

Important Files Changed

Filename Overview
lib/storage.ts adds detectOpencodeAccountPoolPath, assessOpencodeAccountPool, pruneAutoGeneratedSnapshots, snapshotAccountStorage marker writes, equalsResolvedStoragePath, and self-import guard to importAccounts; concurrency guard is correct but pruneAutoGeneratedSnapshots defaults keepLatestPerReason to 1 instead of ACCOUNT_SNAPSHOT_RETENTION_PER_REASON (3), and the redact regex now over-matches single-segment paths without a comment explaining why
lib/codex-manager.ts adds import-opencode branch to the login loop; currentStorage is correctly refreshed each inner-loop iteration via loadAccounts(); invalid-pool errors are logged to console.log instead of console.error, creating a channel mismatch with the throw/import-failure paths
lib/cli.ts adds "import-opencode" to the LoginMode union and handles "i" / "import-opencode" aliases in promptLoginModeFallback; clean and minimal
lib/ui/copy.ts updates selectModePrompt and invalidModePrompt to include the new (i) import OpenCode option; matches the hotkey added in cli.ts
test/storage.test.ts adds comprehensive coverage for detectOpencodeAccountPoolPath (env override, missing file throw, homedir fallback, relative APPDATA), assessOpencodeAccountPool (valid pool, self-import rejection, malformed source), pruneAutoGeneratedSnapshots (retention per reason, storage path scoping, preserveNames, rollback history scanning, concurrent deduplication), and importAccounts self-import guard
test/codex-manager-cli.test.ts adds 7 new test cases covering the full import-opencode menu flow including success, self-import rejection (using mockRejectedValueOnce + errorSpy as recommended by previous review), declined confirmation, failed import with windows path redaction, failed assessment, no pool detected, and limit-exceeded/ineligible assessment variants
test/cli.test.ts adds fallback alias tests for "i" and "import-opencode" inputs; straightforward and correct
test/documentation.test.ts adds docs/getting-started.md to the allowedOpencodeFiles set to permit the new OpenCode import paragraph; matches the doc change
docs/getting-started.md adds a paragraph describing the recovery import option and CODEX_OPENCODE_POOL_PATH; accurate and concise

Sequence Diagram

sequenceDiagram
    participant User
    participant LoginLoop as runAuthLogin (loop)
    participant Storage as lib/storage.ts
    participant FS as Filesystem

    User->>LoginLoop: selects "i" / import-opencode
    LoginLoop->>Storage: assessOpencodeAccountPool({ currentStorage })
    Storage->>FS: detectOpencodeAccountPoolPath()<br/>(env override OR LOCALAPPDATA/APPDATA/.opencode)
    alt CODEX_OPENCODE_POOL_PATH set but missing
        Storage-->>LoginLoop: throws Error (redacted path)
        LoginLoop->>User: console.error "Import assessment failed: ..."
    end
    alt path equals active storage file
        Storage-->>LoginLoop: throws "Import source cannot be the active storage file."
        LoginLoop->>User: console.error "Import assessment failed: ..."
    end
    Storage->>FS: loadBackupCandidate(resolvedPath)
    Storage-->>LoginLoop: BackupRestoreAssessment | null
    alt null (no pool found)
        LoginLoop->>User: console.log "No OpenCode account pool was detected."
    end
    alt !backup.valid || !eligibleForRestore
        LoginLoop->>User: console.log redacted error
    end
    alt wouldExceedLimit
        LoginLoop->>User: console.log limit message
    end
    LoginLoop->>User: confirm "Import OpenCode accounts from <file>?"
    alt confirmed
        LoginLoop->>Storage: importAccounts(assessment.backup.path)
        Storage->>FS: equalsResolvedStoragePath guard (self-import check)
        Storage->>FS: loadImportableBackupCandidate
        Storage->>FS: snapshotAccountStorage (pre-import backup)
        Storage->>FS: writeAutoSnapshotMarker
        Storage->>FS: enforceSnapshotRetention → pruneAutoGeneratedSnapshots(keepLatest=3)
        Storage->>FS: write merged accounts
        Storage-->>LoginLoop: { imported, skipped, total }
        LoginLoop->>User: console.log import summary
    end
    LoginLoop->>LoginLoop: continue (reload storage next iteration)
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: 2468-2471

Comment:
**`pruneAutoGeneratedSnapshots` default retention inconsistent with exported constant**

`keepLatestPerReason` defaults to `1` here, but the system retention policy is `ACCOUNT_SNAPSHOT_RETENTION_PER_REASON = 3`. `enforceSnapshotRetention` always passes the constant explicitly, so the production path is correct — but any direct caller of the exported `pruneAutoGeneratedSnapshots()` without specifying `keepLatestPerReason` silently applies a much more aggressive policy (1 kept per reason vs. 3). on windows this could prune a rollback snapshot that a concurrent `snapshotAccountStorage` just wrote before its marker was registered, creating a silent data-loss window if the in-flight guard is bypassed by a path-key mismatch.

the default should align with the exported constant:

```suggestion
		const keepLatestPerReason = Math.max(
			1,
			options.keepLatestPerReason ?? ACCOUNT_SNAPSHOT_RETENTION_PER_REASON,
		);
```

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/codex-manager.ts
Line: 4470-4476

Comment:
**error channel mismatch for invalid-pool case**

invalid/ineligible pool errors are emitted on `console.log`, while assessment-thrown errors and import failures go to `console.error`. a user who explicitly set `CODEX_OPENCODE_POOL_PATH` to a corrupt file would see the error on stdout instead of stderr, which is surprising and can silently vanish when stdout is redirected. this is particularly relevant on windows where stdout/stderr are often captured separately by shell wrappers.

suggest routing to `console.error` here to match the catch-branch channel:

```suggestion
			if (!assessment.backup.valid || !assessment.eligibleForRestore) {
				const assessmentErrorLabel = formatRedactedFilesystemError(
					assessment.error || "OpenCode account pool is not importable.",
				);
				console.error(assessmentErrorLabel);
				continue;
			}
```

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: 2545-2549

Comment:
**`redactFilesystemDetails` regex now matches single-segment absolute paths**

changing `+` to `*` in the trailing repeat group allows the pattern to match single-component absolute paths like `/tmp` or `C:\file.json`. the change is intentional (tested by the `"redacts single-segment filesystem paths"` case) and handles windows root-level paths correctly. however it also means bare unix paths like `/data` in a non-path context (e.g. a docker volume name in an error message) will now be silently reduced to just `data`. worth a note in a comment so future readers understand why `*` rather than `+` is used:

```suggestion
	return value.replace(
		// * (not +) intentionally matches single-segment absolute paths, e.g. C:\file.json
		/(?:[A-Za-z]:)?[\\/][^"'`\r\n]+(?:[\\/][^"'`\r\n]+)*/g,
		(pathValue) => extractPathTail(pathValue),
	);
```

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

Last reviewed commit: "test(login): cover s..."

Greptile also left 1 inline comment on this PR.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Risk Assessment: Medium with strong data-loss and concurrency safeguards

This PR implements hardened OpenCode import flow with critical safety mechanisms: equalsResolvedStoragePath() enforces self-import rejection in both assessOpencodeAccountPool() and importAccounts() (tested at line 1156 and 6308-6356, including Windows path alias handling); module-level Map-based concurrency guard in pruneAutoGeneratedSnapshots() deduplicates in-flight calls (tested at line 2685). Extensive regression test coverage added (+670 lines storage.test.ts with 926 total tests, +408 lines codex-manager-cli.test.ts with 865 total tests) validates snapshot retention per reason, concurrent deduplication, OpenCode pool detection hierarchy, and import failure handling without disrupting login flow. Rollback is straightforward (revert commit 3f06c8e).

Architectural Decision: The concurrency deduplication pattern uses a module-level path-keyed Map to coalesce concurrent prune calls, reducing filesystem churn but requiring callers to understand storage path semantics.

Security & Data-Loss: Self-import rejection via case-insensitive path comparison prevents data loss; formatRedactedFilesystemError() (3 uses in lib/storage.ts) mitigates information leakage in logs. Import failures are caught gracefully without disrupting login menu loop.

Test Quality: Comprehensive coverage includes concurrent transactional updates (line 588), concurrent backup reads (line 2210), serialized restores (line 2263), deduplication (line 2685), Windows path alias rejection (line 6333), and proper mock setup (importAccountsMock configured with both mockResolvedValue and mockRejectedValueOnce throughout). The prior greptile note about mock mismatch in self-import test does not reflect actual implementation quality—mocks are properly configured.

Walkthrough

adds an "import-opencode" login mode that detects an existing OpenCode account pool file, assesses restore eligibility, prompts for confirmation, imports accounts into active storage, and adds snapshot retention/pruning and filesystem-error redaction logic.

Changes

Cohort / File(s) Summary
Documentation
docs/getting-started.md
added guidance to use the login dashboard recovery import when an opencode pool exists.
cli & ui
lib/cli.ts, lib/ui/copy.ts
added import-opencode login mode and shorthand i; updated prompt text and fallback handling.
auth flow
lib/codex-manager.ts
added menuResult.mode === "import-opencode" branch: calls assessOpencodeAccountPool(), validates assessment, checks account limits, prompts with basename(...), runs importAccounts(), handles/import-summary logging and redacted fs errors.
storage/core
lib/storage.ts
added opencode pool detection (detectOpencodeAccountPoolPath()), pool assessment (assessOpencodeAccountPool()), import-source guard (equalsResolvedStoragePath()), snapshot retention (ACCOUNT_SNAPSHOT_RETENTION_PER_REASON, pruneAutoGeneratedSnapshots()), snapshot listing (listAccountSnapshots()), name parsing helpers, and formatRedactedFilesystemError().
unit tests (cli)
test/cli.test.ts
added tests validating fallback resolves "i" and "import-opencode" to { mode: "import-opencode" }.
integration tests (manager)
test/codex-manager-cli.test.ts
extended mocks for assessOpencodeAccountPool/importAccounts; added many tests covering assessment, confirmation, import success/failure, edge cases (no pool, assessment errors, limit-exceeded, active-storage guard).
storage tests
test/storage.test.ts
added tests for snapshot pruning behavior, concurrent prune dedupe, opencode pool detection (env precedence, homedir fallback, relative paths), redaction, and rejecting import-from-active-storage.
documentation tests
test/documentation.test.ts
expanded allowlist to permit "opencode" in docs/getting-started.md as well as docs/reference/storage-paths.md.

Sequence Diagram(s)

sequenceDiagram
    actor user
    participant cli as cli:runAuthLogin()
    participant storage as lib/storage.ts:assessOpencodeAccountPool()
    participant fs as filesystem
    participant importer as lib/storage.ts:importAccounts()

    user->>cli: choose import-opencode (i)
    cli->>storage: assessOpencodeAccountPool({ currentStorage })
    storage->>fs: detectOpencodeAccountPoolPath() / read pool
    fs-->>storage: pool path or error/null
    alt pool found & valid
        storage-->>cli: BackupRestoreAssessment
        cli->>user: confirm import (shows basename)
        alt user confirms
            user-->>cli: yes
            cli->>importer: importAccounts(poolPath)
            importer->>fs: read pool, write accounts
            fs-->>importer: success or error
            importer-->>cli: { imported, skipped, total }
            cli->>user: print import summary
        else user declines
            user-->>cli: no
            cli->>user: return to menu
        end
    else no pool or assessment error
        storage-->>cli: null or redacted error
        cli->>user: log message and return to menu
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

key issues to scrutinize (senior staff engineer view, concise)

  • missing windows regression coverage. test/storage.test.ts covers env fallbacks but does not exercise windows-specific edge cases like 8.3 names, junction symlinks, or weird APPDATA values. see lib/storage.ts:line and test/storage.test.ts:line. add explicit windows-path normalization tests.
  • concurrency risk in snapshot pruning. pruneAutoGeneratedSnapshots() is invoked from snapshot creation without cross-process locking. concurrent snapshotAccountStorage() calls can race on unlinkWithRetry(). see lib/storage.ts:line. consider file locking or stronger atomic list/prune semantics.
  • import-source validation timing. both assessOpencodeAccountPool() and importAccounts() check equalsResolvedStoragePath(), but a race or symlink change between assessment and import could bypass the guard. ensure importAccounts() re-validates immediately before mutation. see lib/codex-manager.ts:line and lib/storage.ts:line.
  • error redaction consistency. formatRedactedFilesystemError() exists, but some call sites may still use String(error). verify all opencode-related error logs use the redaction helper. see lib/storage.ts:line and lib/codex-manager.ts:line.
  • partial-import/account-limit semantics. codex-manager checks counts before import and may report imported/skipped totals that diverge if import fails mid-stream. consider a dry-run or transactional import/reporting. see lib/codex-manager.ts:line.
  • env var trimming. tests assert whitespace-padded CODEX_OPENCODE_POOL_PATH handling; confirm detectOpencodeAccountPoolPath() consistently trims and rejects whitespace-only values. see lib/storage.ts:line and test/storage.test.ts:line.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format with type(scope): summary, uses lowercase imperative, and is 39 characters—well under the 72-char limit.
Description check ✅ Passed Description covers summary, what changed, validation checklist (mostly checked), docs/governance checklist (docs/getting-started.md checked), and risk/rollback sections with appropriate detail.

✏️ 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 clean/pr102-opencode-import-replay-final
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch clean/pr102-opencode-import-replay-final
📝 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
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 2583-2596: The self-import guard in assessOpencodeAccountPool
currently compares the resolvedPath to getStoragePath() raw, which can be
bypassed by relative/case/separator aliases; update the check to canonicalize
both sides (e.g., run resolvePath/normalize/canonicalize on getStoragePath() as
well) before calling equalsResolvedStoragePath or replace the comparison with a
canonical-equality helper and reuse it for the other occurrences noted (the same
logic around equalsResolvedStoragePath usage at the other locations). Add a
Vitest regression that attempts to import using a Windows-style alias (case-only
or different separators, e.g., backslashes vs forward slashes) to ensure the
guard blocks self-import, and ensure any helper you add is used in
assessOpencodeAccountPool and the other mentioned call sites so the behavior is
consistent.
- Around line 2319-2329: The pruning logic incorrectly treats any object
matching the filename regex as an auto-generated snapshot; update
isAccountSnapshotName and getAccountSnapshotReason usage so pruning (the
deletion block around functions handling 2399-2458) does NOT delete based on
filename shape alone—instead require a reliable marker such as object
metadata/tagging (e.g., a metadata key like autoGenerated=true) or a dedicated
manifest entry that exportNamedBackup sets when creating auto snapshots; change
exportNamedBackup to set that marker for auto snapshots, update the pruning
routine to check that metadata/tag before considering an item for deletion, and
add/adjust vitest references (including the existing storage tests) to assert
manual backups with snapshot-shaped names are preserved and that pruning uses
the metadata gate.
- Around line 2500-2504: enforceSnapshotRetention currently calls
pruneAutoGeneratedSnapshots without the resolved storage root, causing retention
to run against getStoragePath() instead of the transaction-scoped storage root;
update enforceSnapshotRetention (and the similar call around the other
occurrence) to accept and pass the previously computed resolvedStoragePath (or a
storagePath parameter) into pruneAutoGeneratedSnapshots so pruning is pinned to
the same storage root used for the snapshot write (reference function names:
enforceSnapshotRetention, pruneAutoGeneratedSnapshots, getStoragePath,
resolvedStoragePath); update any callers to thread-through the
storagePath/transaction scope and run/adjust the vitest suites (tests around
storage snapshot pinning) to assert retention remains pinned, and ensure the
storage queue logic still handles EBUSY/429 retry semantics where applicable.

In `@test/codex-manager-cli.test.ts`:
- Around line 1081-1127: Add a deterministic test that covers the same-file
import guard by simulating assessOpencodeAccountPoolMock returning a backup
whose path equals the current storage path (use getStoragePathMock() value),
then run runCodexMultiAuthCli(["auth","login"]) and assert that
importAccountsMock and confirmMock are NOT called (and that
assessOpencodeAccountPoolMock was called once); this ensures the code branch in
the login flow that skips self-import when backup.path === getStoragePathMock()
(and any Windows normalization/case-folding) is exercised and protected against
regressions.
- Around line 1481-1518: The test currently injects already-sanitized errors
into assessOpencodeAccountPoolMock so it won't catch redaction regressions;
update the mock response in the assessOpencodeAccountPoolMock.mockResolvedValue
call to use a windows-style full path for both loadError and error (e.g.
"ENOENT: C:\\some\\path\\openai-codex-accounts.json") so the redaction layer
must strip to the basename, then update the assertion on console.log (in the
runCodexMultiAuthCli flow) to expect only "openai-codex-accounts.json" to ensure
only the basename survives; reference assessOpencodeAccountPoolMock,
runCodexMultiAuthCli, and logSpy when making the change.
🪄 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: ae72cf23-0c27-4851-bf34-94612a4e9da6

📥 Commits

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

📒 Files selected for processing (9)
  • docs/getting-started.md
  • lib/cli.ts
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/ui/copy.ts
  • test/cli.test.ts
  • test/codex-manager-cli.test.ts
  • test/documentation.test.ts
  • test/storage.test.ts
👮 Files not reviewed due to content moderation or server errors (6)
  • test/documentation.test.ts
  • test/cli.test.ts
  • lib/cli.ts
  • lib/ui/copy.ts
  • docs/getting-started.md
  • lib/codex-manager.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
lib/**

⚙️ CodeRabbit configuration file

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

Files:

  • lib/ui/copy.ts
  • lib/cli.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/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/documentation.test.ts
  • test/cli.test.ts
  • test/storage.test.ts
  • test/codex-manager-cli.test.ts
🔇 Additional comments (1)
test/codex-manager-cli.test.ts (1)

1289-1315: good windows-path regression here.

test/codex-manager-cli.test.ts:1289-1315 drives a real windows-looking path through the import failure branch and asserts the emitted error only keeps the basename. that is the right shape of regression coverage for this flow.

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."

@ndycode ndycode force-pushed the clean/pr102-opencode-import-replay-final branch from 3f06c8e to e407350 Compare March 19, 2026 15:55
@ndycode ndycode closed this Mar 19, 2026
Comment on lines +2468 to +2471
const keepLatestPerReason = Math.max(
1,
options.keepLatestPerReason ?? 1,
);
Copy link

Choose a reason for hiding this comment

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

P1 pruneAutoGeneratedSnapshots default retention inconsistent with exported constant

keepLatestPerReason defaults to 1 here, but the system retention policy is ACCOUNT_SNAPSHOT_RETENTION_PER_REASON = 3. enforceSnapshotRetention always passes the constant explicitly, so the production path is correct — but any direct caller of the exported pruneAutoGeneratedSnapshots() without specifying keepLatestPerReason silently applies a much more aggressive policy (1 kept per reason vs. 3). on windows this could prune a rollback snapshot that a concurrent snapshotAccountStorage just wrote before its marker was registered, creating a silent data-loss window if the in-flight guard is bypassed by a path-key mismatch.

the default should align with the exported constant:

Suggested change
const keepLatestPerReason = Math.max(
1,
options.keepLatestPerReason ?? 1,
);
const keepLatestPerReason = Math.max(
1,
options.keepLatestPerReason ?? ACCOUNT_SNAPSHOT_RETENTION_PER_REASON,
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/storage.ts
Line: 2468-2471

Comment:
**`pruneAutoGeneratedSnapshots` default retention inconsistent with exported constant**

`keepLatestPerReason` defaults to `1` here, but the system retention policy is `ACCOUNT_SNAPSHOT_RETENTION_PER_REASON = 3`. `enforceSnapshotRetention` always passes the constant explicitly, so the production path is correct — but any direct caller of the exported `pruneAutoGeneratedSnapshots()` without specifying `keepLatestPerReason` silently applies a much more aggressive policy (1 kept per reason vs. 3). on windows this could prune a rollback snapshot that a concurrent `snapshotAccountStorage` just wrote before its marker was registered, creating a silent data-loss window if the in-flight guard is bypassed by a path-key mismatch.

the default should align with the exported constant:

```suggestion
		const keepLatestPerReason = Math.max(
			1,
			options.keepLatestPerReason ?? ACCOUNT_SNAPSHOT_RETENTION_PER_REASON,
		);
```

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

Fix in Codex

@ndycode ndycode deleted the clean/pr102-opencode-import-replay-final branch March 19, 2026 16:49
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