Skip to content

feat(wsl): convert UNC Windows-drive projects to drive paths across a restart (D1c-4c) - #33135

Merged
nwparker merged 11 commits into
mainfrom
nwparker/wsl-d1c-4c-unc-conversion-run
Oct 29, 2026
Merged

nwparker merged 11 commits into
mainfrom
nwparker/wsl-d1c-4c-unc-conversion-run

Conversation

@nwparker

@nwparker nwparker commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​774 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​773
Prod 16 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​985 $\color{#cf222e}{\Huge{\mathbf{−}}}$​28 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​957

ELI5

This PR does the conversion that #33128 only computed. When someone converts a project that Orca has open through WSL's view of a Windows drive (\\wsl.localhost\Ubuntu\mnt\c\src\app), Orca:

  1. checks the project;
  2. puts its terminals to sleep, keeping each shell's folder;
  3. writes down what it is about to do;
  4. restarts.

On the way back up, before the window opens, Orca renames everything to C:\src\app: tabs, layouts, worktree settings, sleeping agents, chats, orchestration history, and each slept terminal's saved history. Panes then wake into their old scrollback and folder, and Git runs on Windows. A crash at any point either finishes the conversion at the next start or leaves everything as it was.

What Changed

  • Before: these projects can't be converted. The WSL mover refuses them, and they keep slow WSL Git (R36).
  • After (behind the internal WSL hosts gate, Windows only): wslDriveUnc:convert converts them. The settings button and the renderer-side stores come in the next PR.
  • How:
    • Start (src/main/wsl/wsl-drive-unc-conversion-run.ts):
    • Journal (wsl-drive-unc-conversion-journal.ts): built on the mover's createMigrationJournalDirectory (durable, owner-only, fails closed), in its own wsl-drive-unc-conversions/ directory.
    • Fence: while an entry is prepared, the mover's fence keeps the projects read-only, with no new terminals (withWslDriveUncConversions in wsl-move-fence.ts). A terminal started in that gap would keep the old id.
    • Startup (wsl-drive-unc-conversion-startup.ts): runs in main-process-ready-foundation.ts before the profile store opens.
      • The profile write is the commit point. It goes through the profile-transfer path: one SQLite transaction with an expected revision (writeProfileProjectDomainChanges), and only the fields the rewrite changed are written.
      • A failure before that commit drops the journal, and nothing has changed (rolled-back).
      • After it, each other store runs in order and is recorded in the journal's ledger. A failed store is retried at the next start (resuming).
      • A crash between the profile commit and the journal update is safe: the rewrite is idempotent, and the id map was journaled before the commit.
    • Stores outside the profile (wsl-drive-unc-conversion-stores.ts):
      • Terminal sessions: each daemon session folder terminal-history/<session id> is renamed to the new id. A slept pane reattaches by its id, so it wakes with its scrollback and folder. The folder holds no id of its own.
      • Shell history: bash/zsh folders under terminal-history*/<hash(worktree id)> move to the new hash, and their meta.json is updated.
      • Orchestration history: reuses the mover's rewriteWslMoveOrchestrationHistory, now exported as a file-level function.
      • Chat journal: journal_sessions.workspace_id and each record's location.workspaceId, in one transaction. A record this build can't read stays byte-identical.

Why

  • Owner decision: these projects become Windows projects with WSL terminals.

  • Why a restart instead of rewriting live. This touches every saved id at once. Doing it before anything opens means:

    • one atomic profile write;
    • no window to hold off;
    • no live terminals to detach;
    • no service with the chat or orchestration database open.

    Profile transfer already restarts the same way.

  • No undo, on purpose. Once the profile write commits, the conversion is one-way, like the decision not to build a reverse mover. Going back would mean re-keying every id to a UNC path that WSL 2.7.11 no longer lets Windows open. A failure before the commit leaves everything as it was.

  • Why not reuse the mover's commit and desktop-rewrite pipeline. It is built around a destination host: a manifest, staging, a host commit, and a re-seeded host partition. Here nothing leaves this computer. So this reuses the mover's pieces (pause, fence, journal directory, orchestration rewrite) and the profile-transfer write, rather than a second copy of the pipeline.

Linked Issue

Part of the WSL re-write, step D1c-4 (risk rows R36, R6). Stacked on #33128.

Visual Proof

N/A: the IPC has no UI yet (next PR).

Testing

  • Automated tests:
    • wsl-drive-unc-conversion-startup.test.ts, against a real profile SQLite database, terminal history folders and a chat journal database. It covers:
      • the full conversion, checking that only the re-keyed fields were written;
      • rollback on a collision;
      • a failed store resuming at the next start;
      • a crash after the profile commit.
    • wsl-drive-unc-conversion-run.test.ts covers:
      • the journal and pause;
      • the WSL-made worktree, out-of-scope repo and missing Git refusals;
      • the seeded real-host shape: a saved UNC worktree with tabs beside the bare drive row discovery wrote converts, and differing data refuses with its cause and key;
      • every UNC path throwing EPERM (WSL 2.7.11 denies \\wsl.localhost\<distro>\mnt\c\… to Windows): the checks only touch the C:\ side;
      • busy terminals;
      • a second conversion while one is waiting;
      • the fence before and after the profile rewrite.
  • pnpm tc:node, plus the mover, IPC, profile and startup suites (7.5k tests), pass locally.
  • Manual on Windows: not run. No Windows box is assigned to this work, and the shared ones are off limits.

Review

Self-reviewed.

Agent skill upstream boundary

  • Not applicable.

Notes

  • Flag-off: no change. The new startup step returns before reading anything unless it is Windows with the gate open (wsl-drive-unc-conversion-startup.ts), and the new IPC refuses while the gate is closed. The one ungated addition is the mover fence's existsSync on the conversion-journal folder, which only the gated convert handler can create, so it returns nothing. Everything else is a refactor or a type. The meta.json handling and the narrower pending check run only inside the gated conversion. Edge case, internal testers only: a journal left prepared by a run with the gate open keeps fencing its projects after the gate closes, because the startup step that finishes it is skipped. That fails safe.
  • Known gaps (follow-ups):
    • The renderer's local stores (quick-open history, notebook kernels, file view preferences) come in the next PR.
    • Fish history is kept by fish under its session name, so it doesn't carry over. The mover doesn't carry it either.
    • An edit refused in the second before the restart shows the mover's "moving to its WSL host" message.
  • SSH and paired hosts: untouched; local projects only.
  • Wire compatibility: none. These are desktop-only IPC, and the profile shape is unchanged.
  • Downgrade: an older build reads the converted profile as an ordinary C:\ project on WSL. It ignores the journal directory.

Status

  • 2026-10-28, head 74d1a1b05bc: rebased onto main 64344142fa0, which has feat(wsl): run Git for Windows for a WSL project on a Windows drive (D1c-2) #33090 squash-merged (fix(wsl-host): tell users how to fix a distro Orca can't host (D1c-1) #33085 earlier) and fix(wsl-mover): hold an app update install while a moved project comes back #35357; feat(wsl): offer Git for Windows in a Windows-drive WSL project's settings (D1c-3) #33097 now targets main. Every commit is kept. CI: running on this head.
  • Safety review fix: a shell-history meta.json that a crash left torn is skipped instead of throwing, so a conversion can no longer stall before re-keying chats. The file is now written atomically (temp file, fsync, rename via writeFileDurableSync). A new conversion is refused only while this profile has a prepared journal. Tests cover a torn meta.json, the startup step leaving a journal untouched with the gate closed or off Windows, a profile-rewritten journal not blocking a new conversion, and the IPC handlers (refused with the gate closed or off Windows, parsed for the active profile, invalid arguments rejected). The optional Low item (a failed history rename on the failure path) is not done.
  • Re-review: only this profile's waiting conversion refuses a new one as conversion-pending (the same per-profile filter startup uses). Test added; it compares the two journals without depending on directory order, which differs between filesystems.
  • Review fixes: a failure writing the journal after the commit leaves it prepared and reports resuming, never a false rolled-back (test throws on that write). The journal records profileId, and startup runs only the active profile's conversions. The run reads the distro's drive mounts for the scope check.
  • Added here: the conversion's pause connects to a WSL host as a user action, the mode Land the WSL project mover dark (merges only after verify-15b, the flag-off audit and green CI) #34059's mover now asks for (it would not typecheck otherwise). Kept from the previous owner: the refusal names its cause, startup rolls back only when the drive key holds different user data, and a test that the conversion runs Windows Git only on the drive path.
  • pnpm tc, pnpm run typecheck:ci, the PR's tests, check:code-quality:changed, the anti-slop oxlint, repo oxlint and ast-grep (including the no-real-time-sleep test rules) and the e2e typecheck ratchet all pass locally on this head, run one branch at a time.

@nwparker
nwparker marked this pull request as ready for review October 24, 2026 11:03
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from f450baa to ae0edfe Compare October 24, 2026 11:29
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from d4156d0 to 56c1025 Compare October 25, 2026 02:01
@nwparker
nwparker requested a review from brennanb2025 as a October 25, 2026 02:01
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from ae0edfe to 2195374 Compare October 25, 2026 02:01
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 56c1025 to cf3d9bc Compare October 25, 2026 02:30
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 2195374 to b34a214 Compare October 25, 2026 02:31
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from cf3d9bc to f0a703e Compare October 25, 2026 04:34
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from b34a214 to 58e5339 Compare October 25, 2026 04:35
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from f0a703e to d34f97f Compare October 25, 2026 06:40
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 58e5339 to 54befbd Compare October 25, 2026 06:40
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from d34f97f to 5783d45 Compare October 25, 2026 08:30
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 54befbd to a53538e Compare October 25, 2026 08:30
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 5783d45 to 0a32b80 Compare October 25, 2026 09:48
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from a53538e to 9f1192c Compare October 25, 2026 09:48
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 0a32b80 to f87d599 Compare October 25, 2026 10:21
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 9f1192c to c728220 Compare October 25, 2026 10:21
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from f87d599 to 9eae9cd Compare October 25, 2026 11:10
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from c728220 to c3e6842 Compare October 25, 2026 11:10
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 9eae9cd to 9f518c3 Compare October 25, 2026 12:00
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from c3e6842 to 914b6c2 Compare October 25, 2026 12:00
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 9f518c3 to b96ac8b Compare October 25, 2026 12:36
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 914b6c2 to 05fef6a Compare October 25, 2026 12:36
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from b96ac8b to 33734d6 Compare October 25, 2026 13:03
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 05fef6a to 2112bdc Compare October 25, 2026 13:03
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 33734d6 to a482987 Compare October 25, 2026 13:16
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 2112bdc to 38da560 Compare October 25, 2026 13:16
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from a482987 to dcc2604 Compare October 27, 2026 23:53
Comment on lines +70 to +72
} catch (error) {
removeWslDriveUncConversion(deps.userDataPath, migrationId)
return { migrationId, outcome: 'rolled-back', detail: describe(error) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Uncertain commits lose their journal

rewriteProfile throwing does not prove the profile rolled back. The profile writer can throw ProfileStateIndeterminateWriteError when COMMIT ends without a confirmed rollback. This catch still deletes the conversion journal.

If the commit took effect, the profile uses drive paths, but terminal history, chats, and the other stores keep their old IDs with no journal left to retry them. Keep the journal when the write result is uncertain and check the saved profile before deciding to roll back.

Knowledge Base Used: Desktop persistence and lifecycle

Comment on lines +100 to +105
if (existsSync(from) && !existsSync(to)) {
renameSync(from, to)
renamed = true
}
// The history sweep keeps a folder whose meta names a live worktree.
rewriteShellHistoryMeta(to, oldId, newId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Retries abandon old shell history

If shell-history fails after the profile commits, startup continues and the project can open new terminals. A new terminal creates the history folder for the new worktree ID. On the next start, this check skips the old folder because the destination exists, then marks the store complete without copying its history.

The old folder still names the old worktree and can be deleted by history cleanup. Keep affected terminals blocked until their history moves, or merge both folders safely before recording completion. The same skip-existing check also appears in renameTerminalSessions.

Knowledge Base Used: Desktop persistence and lifecycle

@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 2f0aeb3 to c3f6175 Compare October 28, 2026 22:47
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from b9ca555 to 74d1a1b Compare October 28, 2026 22:47
OrcaWin added a commit that referenced this pull request Oct 28, 2026
…al: it waits for the window, and finishes on any platform once the gate is open
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from c3f6175 to 7ab29a6 Compare October 29, 2026 01:16
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 74d1a1b to 8e4c006 Compare October 29, 2026 01:16
OrcaWin added a commit that referenced this pull request Oct 29, 2026
…al: it waits for the window, and finishes on any platform once the gate is open
@nwparker
nwparker changed the base branch from nwparker/wsl-d1c-4b-unc-rewrite to main October 29, 2026 01:52
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 82d21727-c351-4d32-9bdb-22655210e4ce
Commits

Reviewing files that changed from the base of the PR and between dc4a990 and c675fa9.

Files selected for processing: 20
  • src/main/app-relaunch.ts
  • src/main/ipc/register-core-handlers/register-core-handlers.ts
  • src/main/ipc/wsl-drive-project-handlers.test.ts
  • src/main/ipc/wsl-drive-project-handlers.ts
  • src/main/ipc/wsl-drive-unc-conversion-handlers.test.ts
  • src/main/ipc/wsl-drive-unc-conversion-handlers.ts
  • src/main/orca-profiles/profile-project-domain-changes.ts
  • src/main/persistence/moving-wsl-projects/wsl-move-fence.ts
  • src/main/persistence/moving-wsl-projects/wsl-move-orchestration-history-rewrite.ts
  • src/main/persistence/moving-wsl-projects/wsl-move-scope-guard.ts
  • src/main/startup/main-process-ready-foundation.ts
  • src/main/wsl/wsl-drive-project-windows-git.ts
  • src/main/wsl/wsl-drive-unc-conversion-journal.ts
  • src/main/wsl/wsl-drive-unc-conversion-run.test.ts
  • src/main/wsl/wsl-drive-unc-conversion-run.ts
  • src/main/wsl/wsl-drive-unc-conversion-startup.test.ts
  • src/main/wsl/wsl-drive-unc-conversion-startup.ts
  • src/main/wsl/wsl-drive-unc-conversion-stores.ts
  • src/main/wsl/wsl-drive-unc-state-rewrite.ts
  • src/shared/wsl-drive-unc-conversion-types.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 8e4c006 to 7fb0b5b Compare October 29, 2026 02:02
nwparker pushed a commit that referenced this pull request Oct 29, 2026
…al: it waits for the window, and finishes on any platform once the gate is open
nwparker and others added 11 commits October 28, 2026 19:19
…nce WSL denies UNC drive folders

WSL 2.7.11 answers EPERM for \\wsl.localhost\<distro>\mnt\c\... from Windows. Pin that the
preflight's Git runs and worktree-link reads use C:\... by making every UNC access throw EPERM.
A refusal over saved state now says whether a project also holds an unconverted repo, or a drive
key already holds different data (and which key), instead of a bare collision code. The fields are
optional, so the window's copy still reads an older host's refusal. The seeded shape (a saved UNC
worktree with tabs beside the bare drive row startup discovery wrote) now converts.
…on, as #34059's mover now requires

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit bfeb4fe3ae7f278ac452e376d426315dbf7b985c)
…and check its profile; refuse paths off a Windows drive mount

- The journal's post-commit phase write sat inside the rollback catch, so a failure there
  dropped the journal and reported rolled-back for a profile already rewritten. The write now
  follows the commit outside the catch: a failure leaves the journal 'prepared', whose rewrite
  is idempotent, and reports resuming.
- The journal records the profile it rewrites, and startup runs only the active profile's.
- The run reads the distro's Windows-drive mounts and passes them to the scope.

Co-Authored-By: Claude <noreply@anthropic.com>
…t is written atomically, and only a prepared conversion blocks a new one
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4c-unc-conversion-run branch from 7fb0b5b to c675fa9 Compare October 29, 2026 02:26
return wslMoveFenceFromCutovers(scanWslMoveCutovers(userDataPath))
return withWslDriveUncConversions(
wslMoveFenceFromCutovers(scanWslMoveCutovers(userDataPath)),
listWslDriveUncConversions(userDataPath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Other profiles lose terminal access

resolveWslMoveFence loads every profile's conversion journal, but startup only finishes the active profile's conversions. If an interrupted conversion leaves profile A's journal prepared, switching to profile B still blocks A's project IDs and folders. A project shared by both profiles can no longer open terminals in B, even though B has no pending conversion.

Filter conversion journals by the active profile when building the fence, matching startup.

nwparker pushed a commit that referenced this pull request Oct 29, 2026
…al: it waits for the window, and finishes on any platform once the gate is open
@nwparker
nwparker merged commit fd8f1f9 into main Oct 29, 2026
57 checks passed
nwparker pushed a commit that referenced this pull request Oct 29, 2026
…al: it waits for the window, and finishes on any platform once the gate is open
nwparker added a commit that referenced this pull request Oct 29, 2026
…onvert the window's stores (D1c-4d) (#33143)

* feat(wsl): offer the drive-path conversion in project settings, and convert the window's stores (D1c-4d)

* fix(wsl): tell the user why saved state blocked the drive-path conversion

A refusal over saved state now names the cause: a folder with different details under both its WSL
path and its drive path, or a project that also holds a repository that isn't converting. An older
host's refusal without a cause keeps the general message.

* fix(wsl): explain a conversion refused because the distro's /mnt/<letter> is not that Windows drive

Co-Authored-By: Claude <noreply@anthropic.com>

* test(wsl): give the renderer-rewrite journal fixture the profile the journal now records

* test(wsl): give the e2e conversion journal the profile field the journal now requires

* test(wsl): carry the #33135 startup tests onto the window-store journal: it waits for the window, and finishes on any platform once the gate is open

* test(wsl): off Windows the conversion IPC still reports startup's outcomes, as the journal can finish on any platform

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants