You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(wsl): re-key the profile of UNC Windows-drive projects converting to drive paths (D1c-4b) - #33128
Some projects are open in Orca through WSL's view of a Windows drive, such as \\wsl.localhost\Ubuntu\mnt\c\src\app. The plan is to turn each one into the plain Windows project C:\src\app, keeping its terminals in WSL. Orca names every workspace by its folder path, so the conversion changes every saved name that includes that path: tabs, layouts, editor files, sleeping agents, worktree settings and so on. This PR adds the step that computes those new names. It is pure code and nothing calls it yet. The next PR runs it safely at startup.
What Changed
src/main/wsl/wsl-drive-unc-identity-rewrite.ts maps one saved string from the distro's view of a converting project's folder to the drive path.
Forms it handles:
a bare path (both UNC spellings, and the lowercase comparison form);
a file: URL;
a worktree id, repoId::path, including a folder project's ::workspace: suffix;
a worktree: key, and a local| host-qualified key;
a terminal session id, <worktree id>@@<suffix>, so slept panes keep their history after the conversion;
an editor file id.
Ids are rebuilt with the composite rewriters that the existing mover's export already uses (orcad-migration-path-rewrite.ts; this PR exports its value function).
It changes a string only when that string belongs to a converting project:
an id is changed only when its repo (or folder workspace) is converting;
a bare path is changed only when it is inside one of the converting project's folders;
another host's ssh:/runtime: key is never changed.
User text (titles, drafts, prompts, scrollback) is skipped, using the mover's own list of those keys. Old ids in priorWorktreeIds are kept as written.
A walk over whole values reuses the original object for anything that didn't change. Two keys landing on the same new key is an error.
src/main/wsl/wsl-drive-unc-state-rewrite.ts applies that to the saved profile.
Each PersistedState field has a disposition, either "rewrite" or "keep". It is a typed table, so a new field won't compile until someone decides. Kept as they are:
settings;
other hosts' session partitions;
automation run history;
the retired-name namespace, which is recomputed from the new worktree folder.
The converted projects get { kind: 'wsl', distro, git: 'windows-host' } (feat(wsl): run Git for Windows for a WSL project on a Windows drive (D1c-2) #33090): Git on Windows, with terminals still in the distro. A project that had no setting used to infer its distro from the UNC path, so the setting has to become explicit. An explicit Windows setting stays as it is.
Each converted worktree's metadata gets its old id appended to priorWorktreeIds, so AI Vault and the checks panel still find sessions recorded under it.
A project group whose members all convert gets its parent folder converted too.
It returns the old→new worktree id map for the stores outside the profile (chats, orchestration, terminal history).
A project that has both converting and non-converting repos is refused.
Owner decision: these projects convert to native Windows projects with WSL terminals (risk row R36). Worktree ids are repoId::path, so every id changes.
Why one path-shaped rewrite and not a per-field table. About 100 fields name a path, and the mover keeps them in its own tables. A walk that recognises identity shapes covers all of them, including fields added later. The walk is limited by repo, folder and host, and the typed disposition table above means every new top-level field still needs a decision.
Why not the profile-transfer session extractor. It rebuilds a session out of the rows it moves and drops everything else. This conversion has to keep the whole local session in place and only rename it.
Idempotent. Running it again changes nothing, which lets the next PR resume an interrupted conversion.
Linked Issue
Part of the WSL re-write, step D1c-4 (risk rows R36, R6). Stacked on #33108.
Visual Proof
N/A: no caller yet, so nothing visible changes.
Testing
Automated tests:
wsl-drive-unc-state-rewrite.test.ts builds the mover's heavy and folder fixture profiles twice, once at \\wsl.localhost\Ubuntu\mnt\c\… and once at C:\…, and checks that the rewrite of the first equals the second. That covers every session, UI and catalog row the mover's fixtures carry. It also covers: minted terminal session ids, projects outside the scope, other hosts, settings, runtime choices, idempotence, project split, key collisions, and bare paths outside the project.
wsl-drive-unc-identity-rewrite.test.ts checks each string form, in scope and out.
Scope test for the nesting refusal.
pnpm tc:node, and the mover and orcad-migration suites, pass locally.
Review
Self-reviewed.
Agent skill upstream boundary
Not applicable.
Notes
Flag-off: no change. The new rewrite modules are reachable only from tests on this branch. The SSH migration path rewriter and the WSL canonical form only gained exports; the function bodies are identical, so SSH migration output is unchanged for every input.
SSH and paired hosts: their partitions and keys are untouched.
Folder workspaces: converted with their projects.
Wire compatibility: no wire change, and no caller yet.
Re-review: a test that a repo nested under a duplicate-refused project's folder converts.
Review fixes: a row counts as discovery-only only when workspaceStatus is missing or the default, so a chosen status refuses instead of being dropped (test added). /mnt/<letter> now follows the same case rule as the UNC path folding. A refused duplicate spelling no longer blocks the folder's one converted project as an overlap.
New from the previous owner, kept: drive-keyed rows that startup discovery wrote for a UNC drive project merge into the saved UNC rows instead of refusing. Gated: it runs only inside the conversion, which runs only with WSL hosts on.
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.
This PR adds a pure rewrite from WSL UNC drive paths to Windows drive paths.
A profile rewrite moves converted WSL projects to drive-path identities.
Saved IDs and paths follow a project’s new drive path.
Overlapping WSL projects stay together when conversion scope is chosen.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Saved profile and selected projects] --> B[Collect changed worktree IDs]
B --> C[Remove empty discovery rows]
C --> D[Rewrite saved paths and identities]
D --> E[Save runtime choices for converted repos]
E --> F[Append prior worktree IDs]
F --> G[Return profile and ID map]
The reason will be displayed to describe this comment to others. Learn more.
Other projects lose saved tabs
The walk rewrites paths in projects outside scope. Convert repo A at \\wsl.localhost\Ubuntu\mnt\c\src while nested repo B at \\wsl.localhost\Ubuntu\mnt\c\src\app stays outside the request. B's path becomes C:\src\app, but its worktree IDs stay UNC because ownerInScope rejects B. getRepoMainWorktreeId then produces a key that no longer matches B's saved tabs and metadata.
This is reachable from WslDriveUncConversionRow in PR #33143, which requests one repo. Filter catalog rows by their owner and host before rewriting paths, or refuse requests that would split overlapping projects.
The reason will be displayed to describe this comment to others. Learn more.
Refused projects partly convert
refuseOverlaps checks candidates only against the original refusedRoots. Consider these UNC drive folders:
Refused repo R is at /mnt/c/refused and has a Linux worktree base.
Repo A is at /mnt/c/refused/a, with worktree base /mnt/c/trees/a.
Repo B is at /mnt/c/trees.
A is refused because it overlaps R, but B stays eligible because A's other folder never joins refusedRoots. Converting B then rewrites bare paths under A's worktree base while A's worktree IDs stay unchanged. Folder workspaces also bypass this check because their loop runs afterward.
Include all project folders and repeat the overlap checks until no more projects are refused.
The reason will be displayed to describe this comment to others. Learn more.
Folders containing @@ partly convert
A project path such as \\wsl.localhost\Ubuntu\mnt\c\app@@demo enters the terminal-session branch before the bare-path check. ownerInScope rejects that bare path, so repo.path stays UNC while its worktree IDs become drive-based. bareWorktreeId also excludes these IDs from worktreeIds, so the later history rewrite misses them.
This leaves the project looking up saved workspace keys that no longer match, with history still under the old names. Distinguish bare paths from terminal session IDs and keep valid worktree IDs containing @@ in the returned map.
The change adds project-scope filtering and WSL UNC-to-drive identity rewriting. It adds a persisted-state conversion that updates selected project and workspace data, handles discovery-only metadata rows, and records prior worktree IDs. Tests cover path and identity conversion, scope overlaps, state updates, unchanged values, and collision cases. Two existing helpers are exported for use by the rewriting code.
Priority: Normal
Merge Risk: Low · up to 7ab29
The conversion helper still has scope and identity inconsistencies to fix before startup uses it. With no caller added here, this PR is mergeable with those fixes tracked for the follow-up.
Pre-merge checks: 4 · 1
Failed checks: 1 warning
Check name
Status
Explanation
Resolution
Docstring Coverage
Docstring coverage is 55.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta…
Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks: 4 passed
Check name
Status
Explanation
Title check
The title clearly identifies the WSL profile re-keying change for UNC Windows-drive projects converting to drive paths.
Description check
The description is detailed and covers the change, rationale, linked work, testing, visual proof, compatibility notes, and review status. It does not include the template checklist, but the required i…
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Full details: Description check
Explanation
The description is detailed and covers the change, rationale, linked work, testing, visual proof, compatibility notes, and review status. It does not include the template checklist, but the required information is otherwise substantially complete.
Full details: Docstring Coverage
Explanation
Docstring coverage is 55.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 20 functions across 10 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
Finishing Touches: 💡 1
📝 Generate docstrings: 💡
Commit to this branch
Create a new PR
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.
…oject instead of refusing
WSL Git lists a UNC /mnt/c project's folders under their drive paths, so startup discovery writes a
bare C:\-keyed worktree row (and its identity alias) beside the saved UNC one. The re-key then
landed both on one key and every conversion was refused as wsl_drive_unc_key_collision.
Before the re-key, a drive-keyed row holding only discovery bookkeeping is dropped with its alias
and identity row, so the saved row (instance id, name, links) takes the key. Any other two keys
that meet merge when one is empty or both match; only differing user data refuses, and the error
names the key.
…sion never drops it as a discovery row
Discovery writes only the default status. A drive-keyed row with any other status now counts
as the user's, so the conversion refuses with the key instead of dropping it.
Co-Authored-By: Claude <noreply@anthropic.com>
…folding
Only a lowercase /mnt is drvfs, with the drive letter in either case, as
foldWslUncPathCaseInsensitiveParts already says; /MNT is an ordinary Linux folder.
Co-Authored-By: Claude <noreply@anthropic.com>
Conflicts resolved by hand: none. Every rebase applied cleanly.
Range-diff: every commit is = against its old version, so no PR's content changed.
Load rule: passed on each branch, run serially. That covers tc, typecheck:ci, the e2e typecheck ratchet, the touched tests, check:code-quality:changed, anti-slop oxlint, repo oxlint, ast-grep and the no-real-time-sleep rules.
Push: each branch went up with --force-with-lease, after a fetch confirmed its remote was still at the old head.
Flag-off: unchanged from the last table; all five are "no change".
The reason will be displayed to describe this comment to others. Learn more.
Folder agents switch to Windows
Converting a repo-less folder workspace changes folderPath from an Ubuntu UNC path to C:\..., but withWindowsGit saves the WSL choice only for projects with converted repos. A repo-less folder has no project runtime, so executionRuntimePlatform now selects Windows instead of Linux. New agents run outside the folder's previous WSL environment.
Preserve the folder's distro before changing its path, or refuse this conversion until that choice can be saved.
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.
The reason will be displayed to describe this comment to others. Learn more.
Minor · Functional Correctness · Quick win
Supported by static analysis
Script executed:
set -eu
printf'%s\n''--- scope file at reviewed head ---'
git show 7ab29a678a52eae2317258dab9a27db559922d8b:src/main/wsl/wsl-drive-unc-project-scope.ts | nl -ba | sed -n '1,240p'printf'%s\n''--- identity rewrite file at reviewed head ---'
git show 7ab29a678a52eae2317258dab9a27db559922d8b:src/main/wsl/wsl-drive-unc-identity-rewrite.ts | nl -ba | sed -n '1,240p'printf'%s\n''--- relevant diff from merge base ---'
git diff --unified=30 ccd0829e6f7293b0e0802ef7179271a9e13fe231 7ab29a678a52eae2317258dab9a27db559922d8b -- src/main/wsl/wsl-drive-unc-project-scope.ts src/main/wsl/wsl-drive-unc-identity-rewrite.ts
Repository: stablyai/orca
Length of output: 36529
Script executed:
set -eu
printf'%s\n''--- normalizeRuntimePathForComparison definition ---'
rg -n -F -- 'normalizeRuntimePathForComparison' src/shared src/main | head -40
printf'%s\n''--- cross-platform path implementation ---'
file=$(rg -l -F -- 'export function normalizeRuntimePathForComparison' src/shared src/main | head -1)test -n "$file"
nl -ba "$file"| sed -n '1,240p'printf'%s\n''--- focused tests for the helper ---'
rg -n -F --glob '*.{test,spec}.{ts,tsx,js}' -e 'normalizeRuntimePathForComparison' -e 'C:\\' -e 'c:/' src/shared src/main | head -120 ||test"$?" -eq 1
Repository: stablyai/orca
Length of output: 30403
Handle a drive-root folder in the nests check.
normalizeRuntimePathForComparison('C:/') preserves the drive root as c:/. nests then appends / and checks for c://, so it misses nested paths such as c:/repo.
A refused drive-root repository can therefore fail to block a nested accepted repository. Use the same root-aware boundary rule as createWslDriveUncRewriter.inRoot.
IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
The reason will be displayed to describe this comment to others. Learn more.
Minor · Functional Correctness · Quick win
Refusal is not transitive, so a repo can still convert inside a refused enclosing repo.
refusedRoots is built once, from the original refusals. A repo refused as overlaps-refused-project is never added to it.
Example:
A is /mnt/c/src/app and is refused (mixed locations).
B is /mnt/c/src and is refused because it encloses A.
C is /mnt/c/src/lib and does not overlap A, but it nests inside B.
C stays accepted while its enclosing B stays unconverted. A bare path under C is rewritten even when it belongs to B's state. This is the split that refuseOverlaps is meant to prevent.
Repeat the filter until no new repo is refused.
Proposed fix
--- "a/src/main/wsl/wsl-drive-unc-project-scope.ts"+++ "b/src/main/wsl/wsl-drive-unc-project-scope.ts"@@ -145,14 +145,23 @@
const refusedRoots = repos.filter((repo) => refusedIds.has(repo.id)).flatMap(comparable)
const nests = (left: string, right: string): boolean =>
left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`)
- const overlapping = new Set(- repos- .filter((repo) => scope.repoIds.includes(repo.id))- .filter((repo) =>- comparable(repo).some((root) => refusedRoots.some((refused) => nests(root, refused)))- )- .map((repo) => repo.id)- )+ const overlapping = new Set<string>()+ const accepted = repos.filter((repo) => scope.repoIds.includes(repo.id))+ let grew = true+ while (grew) {+ grew = false+ for (const repo of accepted) {+ if (overlapping.has(repo.id)) {+ continue+ }+ const roots = comparable(repo)+ if (roots.some((root) => refusedRoots.some((refused) => nests(root, refused)))) {+ overlapping.add(repo.id)+ refusedRoots.push(...roots)+ grew = true+ }+ }+ }
scope.repoIds = scope.repoIds.filter((id) => !overlapping.has(id))
for (const id of overlapping) {
scope.refused.push({ kind: 'repo', id, reason: 'overlaps-refused-project' })
Add a three-level nesting test to cover this case.
Committable suggestion
IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
set -eu
printf'%s\n''--- dispositions ---'
nl -ba src/shared/wsl-drive-unc-state-dispositions.ts
printf'%s\n''--- persisted state fields and related types ---'
rg -n -F --glob '*.ts' -e 'export type PersistedState' -e 'export interface PersistedState' -e 'type ProjectHostSetup' -e 'interface ProjectHostSetup' -e 'projectHostSetups:' src/shared src/main | head -120
printf'%s\n''--- candidate declarations ---'
rg -n -F --glob '*.ts' -e 'export type Repo' -e 'export interface Repo' -e 'export type ProjectHostSetup' -e 'export interface ProjectHostSetup' src/shared
Repository: stablyai/orca
Length of output: 14562
Keep refused duplicate rows out of the state rewrite.
rewriteWslDriveUncState rewrites every repos row. A duplicate UNC row outside repoIds can therefore have its bare path converted when it lies under a selected root, while its owner-keyed worktree IDs remain unchanged. Add a regression test for two UNC spellings of one folder and restrict repos and applicable projectHostSetups rows to their scoped owners before applying the generic rewrite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
Some projects are open in Orca through WSL's view of a Windows drive, such as
\\wsl.localhost\Ubuntu\mnt\c\src\app. The plan is to turn each one into the plain Windows projectC:\src\app, keeping its terminals in WSL. Orca names every workspace by its folder path, so the conversion changes every saved name that includes that path: tabs, layouts, editor files, sleeping agents, worktree settings and so on. This PR adds the step that computes those new names. It is pure code and nothing calls it yet. The next PR runs it safely at startup.What Changed
src/main/wsl/wsl-drive-unc-identity-rewrite.tsmaps one saved string from the distro's view of a converting project's folder to the drive path.file:URL;repoId::path, including a folder project's::workspace:suffix;worktree:key, and alocal|host-qualified key;<worktree id>@@<suffix>, so slept panes keep their history after the conversion;orcad-migration-path-rewrite.ts; this PR exports its value function).ssh:/runtime:key is never changed.priorWorktreeIdsare kept as written.src/main/wsl/wsl-drive-unc-state-rewrite.tsapplies that to the saved profile.PersistedStatefield has a disposition, either "rewrite" or "keep". It is a typed table, so a new field won't compile until someone decides. Kept as they are:{ kind: 'wsl', distro, git: 'windows-host' }(feat(wsl): run Git for Windows for a WSL project on a Windows drive (D1c-2) #33090): Git on Windows, with terminals still in the distro. A project that had no setting used to infer its distro from the UNC path, so the setting has to become explicit. An explicit Windows setting stays as it is.priorWorktreeIds, so AI Vault and the checks panel still find sessions recorded under it.wsl-drive-unc-project-scope.ts, from feat(wsl): scope and path map for converting UNC Windows-drive projects (D1c-4a) #33108): a project nested inside, or around, a refused project is now refused too (overlaps-refused-project). Bare paths are re-keyed by the folder they sit in, so nested projects have to convert together.Why
repoId::path, so every id changes.Linked Issue
Part of the WSL re-write, step D1c-4 (risk rows R36, R6). Stacked on #33108.
Visual Proof
N/A: no caller yet, so nothing visible changes.
Testing
wsl-drive-unc-state-rewrite.test.tsbuilds the mover's heavy and folder fixture profiles twice, once at\\wsl.localhost\Ubuntu\mnt\c\…and once atC:\…, and checks that the rewrite of the first equals the second. That covers every session, UI and catalog row the mover's fixtures carry. It also covers: minted terminal session ids, projects outside the scope, other hosts, settings, runtime choices, idempotence, project split, key collisions, and bare paths outside the project.wsl-drive-unc-identity-rewrite.test.tschecks each string form, in scope and out.pnpm tc:node, and the mover and orcad-migration suites, pass locally.Review
Self-reviewed.
Agent skill upstream boundary
Notes
Status
c3f617533ad: rebased onto main64344142fa0, 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.workspaceStatusis missing or the default, so a chosen status refuses instead of being dropped (test added)./mnt/<letter>now follows the same case rule as the UNC path folding. A refused duplicate spelling no longer blocks the folder's one converted project as an overlap.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.