Skip to content

fix: unblock dev storage export and docs links#335

Open
ndycode wants to merge 3 commits intodevfrom
fix/dev-baseline-red
Open

fix: unblock dev storage export and docs links#335
ndycode wants to merge 3 commits intodevfrom
fix/dev-baseline-red

Conversation

@ndycode
Copy link
Copy Markdown
Owner

@ndycode ndycode commented Apr 1, 2026

Summary

  • fix export so an empty current pool rejects instead of exporting from stale restored state
  • point README and docs release-note links at release files that actually exist on dev

Validation

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

two focused fixes: exportAccounts now loads via loadAccountsForExport — a new private function that intentionally skips backup recovery — so a missing primary storage file correctly rejects instead of silently exporting stale backup data. docs and readme release-note links are updated to point at files that actually exist on the dev branch (v1.1.10, v0.1.9, v0.1.8).

  • lib/storage.ts: loadAccountsForExport mirrors loadAccountsInternal except it returns empty storage (not backup recovery) when the primary path is absent; saveAccountsUnlocked is lock-safe when called from inside withStorageLock; no reentrancy or deadlock risk
  • test/storage.test.ts: regression test for backup-only rejection on the no-transaction path; cleanup falls under the existing afterEach fs.rm; the transactionState.active && different-path branch has no corresponding coverage (P2 note)
  • README.md / docs/README.md: broken v1.2.0.md links removed; all three replacement files confirmed present in docs/releases/

Confidence Score: 5/5

safe to merge — fix is correct, no regressions, all changed doc links verified present on dev

the logic change is well-scoped: backup recovery is intentionally removed from the export path, saveAccountsUnlocked is lock-safe inside withStorageLock, and the regression test covers the primary failure mode. the only remaining finding is a P2 coverage gap for the active-transaction-different-path branch, which does not affect merge safety.

test/storage.test.ts — active-transaction-different-path branch has no backup-rejection test

Important Files Changed

Filename Overview
lib/storage.ts adds loadAccountsForExport (no backup recovery) and wires it into exportAccounts; correctly rejects on backup-only or missing primary storage; saveAccountsUnlocked does not re-acquire the lock so no deadlock risk inside withStorageLock
test/storage.test.ts adds regression test for backup-only rejection; cleanup is handled by the existing afterEach fs.rm; active-transaction-different-path branch has no backup-rejection coverage
README.md points release-note links at files that actually exist on dev (v1.1.10, v0.1.9, v0.1.8 all verified present)
docs/README.md same link fix as README.md — removes broken v1.2.0 references in both user-guides and reference tables

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[exportAccounts called] --> B{transactionState.active\nAND same storagePath?}
    B -- yes --> C[use transaction snapshot]
    B -- no --> D{transactionState.active\nAND different storagePath?}
    D -- yes --> E[loadAccountsForExport\nno lock]
    D -- no --> F[withStorageLock\nloadAccountsForExport]
    E --> G[loadAccountsForExport]
    F --> G
    G --> H{reset marker exists?}
    H -- yes --> I[return empty storage]
    H -- no --> J{primary path exists?}
    J -- no --> K[return empty storage\nmissing-storage]
    J -- yes --> L[loadAccountsFromPath]
    L --> M{parse error?}
    M -- ENOENT --> N[return migratedLegacyStorage]
    M -- other error --> O[throw]
    M -- success --> P[maybe save migration\nreturn normalized]
    C --> Q{accounts.length == 0?}
    I --> Q
    K --> Q
    N --> Q
    P --> Q
    Q -- yes --> R[throw: No accounts to export]
    Q -- no --> S[write export file]
Loading

Fix All in Codex

Prompt To Fix All With AI

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

Comment:
**missing coverage: active-transaction-different-path branch**

the new test exercises the no-transaction path (`withStorageLock(loadAccountsForExport)`). the other branch — `transactionState?.active && storagePath !== currentStoragePath` — calls `loadAccountsForExport()` **without** a lock and is not tested. a concurrent write to the storage file during that window could yield stale or partial data, and there's no regression guard for it. worth adding a test that wraps `exportAccounts` inside a `withAccountStorageTransaction` on a *different* storage path and asserts the same backup-only rejection behaviour.

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

Reviews (2): Last reviewed commit: "fix-export-primary-storage-only" | Re-trigger Greptile

@coderabbitai ignore

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

two focused fixes: exportAccounts now loads via loadAccountsForExport, which intentionally skips backup recovery so a missing primary storage file correctly rejects instead of silently exporting stale backup data. doc and readme release-note links are updated to point at files that actually exist on dev (v1.1.10, v0.1.9, v0.1.8).

  • lib/storage.ts: loadAccountsForExport mirrors loadAccountsInternal but removes the backup-candidate recovery loop — backup-only state now correctly yields 0 accounts and throws on export. WAL recovery and legacy migration are preserved intentionally.
  • lib/storage.ts (P2): migrateLegacyProjectStorageIfNeeded(saveAccountsUnlocked) fires at the top of loadAccountsForExport before path checks — on the no-lock branch (active transaction + different path), saveAccountsUnlocked can write to the alternate path without storageMutex. narrow edge case (only when a legacy migration is pending on the alternate path) but relevant on windows.
  • test/storage.test.ts: four new regression tests — backup-only rejection, WAL recovery, legacy migration export, and the previously-uncovered active-transaction-different-path branch — all verified correct.
  • README.md / docs/README.md: broken v1.2.0.md links removed; replacement files confirmed present in docs/releases/.

Confidence Score: 5/5

safe to merge — fix is correct, all three export branches now covered by regression tests, doc links verified present

only remaining finding is a P2 edge case: saveAccountsUnlocked can fire without the mutex on the no-lock branch if a legacy migration is triggered on an alternate path. the scenario requires an active transaction on a different path AND a pending legacy migration simultaneously — too narrow to block merge. all P0/P1 paths are clean.

lib/storage.ts line 1933-1934 — saveAccountsUnlocked without lock on no-lock export branch

Important Files Changed

Filename Overview
lib/storage.ts adds loadAccountsForExport that mirrors loadAccountsInternal but omits backup recovery so a backup-only state correctly rejects; wires it into exportAccounts replacing the old paths; saveAccountsUnlocked is lock-safe inside withStorageLock but can fire without the mutex on the active-transaction-different-path branch if a legacy migration is triggered (P2 edge case)
test/storage.test.ts adds four new export regression tests: backup-only rejection (no-transaction), WAL recovery, legacy migration, and active-transaction-different-path backup-only rejection; covers all three branches of the new exportAccounts logic
README.md replaces broken v1.2.0 release-note links with v1.1.10, v0.1.9, v0.1.8 — all three files confirmed present in docs/releases/
docs/README.md removes two broken v1.2.0 references in user-guides and reference tables; replaces with v1.1.10 which exists on disk

Sequence Diagram

sequenceDiagram
    participant caller
    participant exportAccounts
    participant loadAccountsForExport
    participant withStorageLock

    caller->>exportAccounts: exportAccounts(filePath)
    exportAccounts->>exportAccounts: read transactionSnapshotContext

    alt same-path active transaction
        exportAccounts->>exportAccounts: use transactionState.snapshot
    else different-path active transaction (no lock)
        exportAccounts->>loadAccountsForExport: loadAccountsForExport()
        loadAccountsForExport->>loadAccountsForExport: migrate legacy (saveAccountsUnlocked, no mutex)
        loadAccountsForExport->>loadAccountsForExport: check reset marker / primary path / WAL
        loadAccountsForExport-->>exportAccounts: storage | null
    else no active transaction
        exportAccounts->>withStorageLock: withStorageLock(loadAccountsForExport)
        withStorageLock->>loadAccountsForExport: loadAccountsForExport()
        loadAccountsForExport->>loadAccountsForExport: migrate legacy (saveAccountsUnlocked, inside lock)
        loadAccountsForExport->>loadAccountsForExport: check reset marker / primary path / WAL
        loadAccountsForExport-->>withStorageLock: storage | null
        withStorageLock-->>exportAccounts: storage | null
    end

    exportAccounts->>exportAccounts: accounts.length == 0? → throw "No accounts to export"
    exportAccounts->>exportAccounts: write export file (mode 0o600)
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: 1933-1934

Comment:
**`saveAccountsUnlocked` called without lock on the no-lock branch**

`migrateLegacyProjectStorageIfNeeded(saveAccountsUnlocked)` runs at the top of `loadAccountsForExport` regardless of which branch reaches it. on the active-transaction-different-path branch (`transactionState?.active && storagePath !== currentStoragePath`), `loadAccountsForExport()` is called **without** holding `storageMutex`. if a legacy migration is found and triggers `saveAccountsUnlocked`, that write to the alternate path has no mutex protection — on windows this can race with any concurrent i/o on that file (antivirus scans, other processes).

the scenario is narrow (active transaction on path A + export of path B that has a pending legacy migration), but the project's `AGENTS.md` calls out windows filesystem races as a first-class concern. consider either: (a) skipping the migration persist during export-only reads (pass `undefined` or a no-op as the persist callback), or (b) noting the assumption that the caller serialises access to `alternateStoragePath` externally.

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

Reviews (3): Last reviewed commit: "fix: restore export recovery paths" | Re-trigger Greptile

Context used:

  • Context used - speak in lowercase, concise sentences. act like th... (source)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

the pr updates release notes documentation links and refactors the storage layer's account loading logic during export operations. a new helper function loadAccountsForExport() consolidates cleanup, migration, validation, and persistence concerns, while exportAccounts() now routes non-transaction-snapshot calls through this new path instead of the previous inline approach.

Changes

Cohort / File(s) Summary
Documentation
README.md, docs/README.md
Updated release notes links: "Current stable" changed from v1.2.0 to v1.1.10, and "Earlier stable" shifted to v0.1.8. Link orderings adjusted across user guides and reference sections.
Storage Layer Refactoring
lib/storage.ts
Introduced loadAccountsForExport() helper that chains cleanup (stale rotating backups), legacy migration, validation, and v1→v3 normalization persistence. Refactored exportAccounts() to use withStorageLock(loadAccountsForExport) for non-transactional paths while preserving snapshot behavior when active transaction exists.
Storage Tests
test/storage.test.ts
Added single test case verifying exportAccounts() rejects with "No accounts to export" when only .bak file exists and primary storage path missing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Notes

  • missing test coverage: only one test case added for backup-only scenario. what about the happy path through loadAccountsForExport()? need tests for: stale backup cleanup actually removing files, legacy migration execution, schema validation warnings, and v1→v3 normalization persistence via saveAccountsUnlocked at lib/storage.ts:line where the new function returns. currently flying blind on whether the normalization actually persists correctly.

  • concurrency risk: loadAccountsForExport() calls withStorageLock(loadAccountsForExport) but the function itself performs disk I/O including loadAccountsFromPath() and saveAccountsUnlocked(). need to verify the lock is held across the entire operation, especially during the migration step. if the lock is released between load and persist, concurrent writers could corrupt the v1→v3 normalization.

  • windows edge case: file artifact cleanup references "stale rotating backup temp artifacts" but no implementation shown. if temp file paths use unix-style separators or don't handle windows paths correctly, cleanup will silently fail on windows and backups will accumulate. verify the cleanup logic handles path separators correctly.

  • intentional reset marker logic: the summary mentions "handles the presence/absence of the intentional reset marker" but doesn't explain what that marker does or what the behavior difference is. code review needs to verify this doesn't accidentally skip required logic when the marker is absent.

Suggested labels

bug

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed title follows conventional commits format with lowercase imperative summary (46 chars), type=fix, clearly describes the two main fixes: unblocking storage export and updating docs links.
Description check ✅ Passed description covers summary and validation with specific commands; however, docs-and-governance checklist is incomplete—README updates are noted but formal checkbox verification missing, and docs/getting-started.md, docs/features.md, and upgrade guidance not explicitly addressed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 fix/dev-baseline-red
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/dev-baseline-red

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.

@chatgpt-codex-connector
Copy link
Copy Markdown

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

@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Apr 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

✅ Actions performed

Review triggered.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
docs/README.md (1)

29-48: 🧹 Nitpick | 🔵 Trivial

inconsistent labeling for v1.1.10.md within this file.

line 29 labels releases/v1.1.10.md as "stable release notes" in the user guides table, but line 48 labels the same file as "current stable release notes" in the reference section. use consistent terminology: either "stable" or "current stable" throughout.

📝 proposed fix for consistency

option 1: align table to reference section (recommended):

-| [releases/v1.1.10.md](releases/v1.1.10.md) | Stable release notes |
+| [releases/v1.1.10.md](releases/v1.1.10.md) | Current stable release notes |

option 2: align reference section to table:

-| [releases/v1.1.10.md](releases/v1.1.10.md) | Current stable release notes |
+| [releases/v1.1.10.md](releases/v1.1.10.md) | Stable release notes |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/README.md` around lines 29 - 48, The entry for releases/v1.1.10.md is
labeled inconsistently; change the label in the user-guides table (the first
table under the guides list that contains "releases/v1.1.10.md") to match the
reference section by renaming its label from "Stable release notes" to "Current
stable release notes" so both occurrences of releases/v1.1.10.md use the same
terminology.
🤖 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 1929-1978: loadAccountsForExport currently returns
"missing-storage" before attempting legacy or WAL recovery and never calls
loadAccountsFromJournal; reorder and extend the control flow so the function
first attempts migrateLegacyProjectStorageIfNeeded and loadAccountsFromPath, and
only if loadAccountsFromPath throws ENOENT after trying loadAccountsFromJournal
(call loadAccountsFromJournal like loadAccountsForImport does) should it fall
back to migratedLegacyStorage or finally return the missing-storage marker;
preserve existing resetMarkerPath checks at the same early and catch locations,
run saveAccountsUnlocked on successful migration as before, and ensure you
reference the functions loadAccountsForExport, loadAccountsFromPath,
loadAccountsFromJournal, migrateLegacyProjectStorageIfNeeded, and
saveAccountsUnlocked when making the changes and updating the vitest cases
around storage.test.ts to cover transient EBUSY/EPERM rename failures and failed
canonical-path persists.

In `@test/storage.test.ts`:
- Around line 959-984: Add a new vitest that exercises the concurrency-sensitive
branch: use withAccountStorageTransaction() (or the helper that triggers it) to
start a transaction on the original storage file, then call
setStoragePathDirect(...) to switch to a second storage file that only has the
`.bak` backup, and finally call exportAccounts(exportPath) and assert it rejects
with /No accounts to export/ and that existsSync(exportPath) is false; this
ensures the branch in lib/storage.ts (the path that skips withStorageLock when a
transaction is active and storage path changes) is covered and deterministic
under vitest.

---

Outside diff comments:
In `@docs/README.md`:
- Around line 29-48: The entry for releases/v1.1.10.md is labeled
inconsistently; change the label in the user-guides table (the first table under
the guides list that contains "releases/v1.1.10.md") to match the reference
section by renaming its label from "Stable release notes" to "Current stable
release notes" so both occurrences of releases/v1.1.10.md use the same
terminology.
🪄 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: c6fed824-745c-41aa-ac94-3620e91541d8

📥 Commits

Reviewing files that changed from the base of the PR and between 1be5e95 and e92d1f8.

📒 Files selected for processing (4)
  • README.md
  • docs/README.md
  • lib/storage.ts
  • test/storage.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
docs/**

⚙️ CodeRabbit configuration file

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

Files:

  • docs/README.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
lib/**

⚙️ CodeRabbit configuration file

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

Files:

  • lib/storage.ts
🔇 Additional comments (2)
docs/README.md (1)

29-31: verify release note files exist and check consistency.

the user guides table references v1.1.10.md, v0.1.9.md, and v0.1.8.md. these match README.md:294-296, which is correct. however, verify the files exist on dev branch (same verification script as README.md applies).

see verification script in README.md:294-296 review comment.

README.md (1)

294-296: release note files at docs/releases/ all exist on dev branch—v1.1.10.md, v0.1.9.md, v0.1.8.md, and v0.1.0-beta.0.md are all present. the link updates at readme.md:294-296 won't introduce broken links.

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