Skip to content

feat(wsl): re-key the profile of UNC Windows-drive projects converting to drive paths (D1c-4b) - #33128

Merged
nwparker merged 7 commits into
mainfrom
nwparker/wsl-d1c-4b-unc-rewrite
Oct 29, 2026
Merged

nwparker merged 7 commits into
mainfrom
nwparker/wsl-d1c-4b-unc-rewrite

Conversation

@nwparker

@nwparker nwparker commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​366 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​365
Prod 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​637 $\color{#cf222e}{\Huge{\mathbf{−}}}$​6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​631

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 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.
  • Scope (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

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

Status

@nwparker
nwparker marked this pull request as ready for review October 24, 2026 11:03
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from 71b61a9 to fae28ea 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-4b-unc-rewrite branch from d4156d0 to 56c1025 Compare October 25, 2026 02:01
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from fae28ea to f5e6b51 Compare October 25, 2026 02:30
@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-4a-unc-drive-scope branch from f5e6b51 to 725f1d4 Compare October 25, 2026 04:34
@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-4a-unc-drive-scope branch from 725f1d4 to 9b2f879 Compare October 25, 2026 06:40
@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-4a-unc-drive-scope branch from 9b2f879 to f82f8a8 Compare October 25, 2026 08:30
@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-4a-unc-drive-scope branch from f82f8a8 to 37f8dd7 Compare October 25, 2026 09:48
@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-4a-unc-drive-scope branch from 37f8dd7 to 73d6c57 Compare October 25, 2026 10:21
@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-4a-unc-drive-scope branch from 73d6c57 to ed9113a Compare October 25, 2026 11:10
@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-4a-unc-drive-scope branch from ed9113a to 67da7de Compare October 25, 2026 11:59
@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-4a-unc-drive-scope branch from 67da7de to 8b6579e Compare October 25, 2026 12:36
@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-4a-unc-drive-scope branch from 8b6579e to c465a8e Compare October 25, 2026 13:03
@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-4a-unc-drive-scope branch from c465a8e to 8796cff Compare October 25, 2026 13:16
@nwparker
nwparker force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 33734d6 to a482987 Compare October 25, 2026 13:16
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from 8796cff to a96c9e4 Compare October 27, 2026 23:53
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from a482987 to dcc2604 Compare October 27, 2026 23:53
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 4ef2933 to fb4885f Compare October 28, 2026 03:17
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from 7388160 to cf08184 Compare October 28, 2026 10:48
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from fb4885f to 81c8773 Compare October 28, 2026 10:48
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from cf08184 to 4912f85 Compare October 28, 2026 11:30
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 81c8773 to 26124b6 Compare October 28, 2026 11:30
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from 4912f85 to 8c12faa Compare October 28, 2026 12:05
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 26124b6 to 7fa0632 Compare October 28, 2026 12:05
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from 8c12faa to 5a931aa Compare October 28, 2026 20:57
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 7fa0632 to 2f0aeb3 Compare October 28, 2026 20:57
@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 1/5

[Medium impact] The rewrite is limited to local WSL project conversion, but spans many saved profile areas.

The PR needs fixes before merging because conversion can split saved project identities and switch repo-less folder agents from WSL to Windows.

Findings

  1. P1 Folder agents switch to Windows ▶
  2. P1 Other projects lose saved tabs ▶
  3. P1 Refused projects partly convert ▶
  4. P1 Folders containing @@ partly convert ▶

Summary

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]
Loading

Reviews (3) · Last reviewed commit: "test(wsl): a project nested under a refu..." · Reviewed by Greptile

Comment on lines +70 to +72
for (const [field, disposition] of Object.entries(WSL_DRIVE_UNC_STATE_DISPOSITIONS)) {
if (disposition === 'rewrite' && isStateField(field) && state[field] !== undefined) {
Object.assign(next, { [field]: rewriteWslDriveUncValue(state[field], rewriter) })

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

Comment on lines +145 to +152
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)))

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

@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4a-unc-drive-scope branch from 5a931aa to 4c5a342 Compare October 28, 2026 22:47
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from 2f0aeb3 to c3f6175 Compare October 28, 2026 22:47
Comment on lines +124 to +128
const session = value.lastIndexOf(PTY_SESSION_ID_SEPARATOR)
if (session > 0) {
return ownerInScope(value.slice(0, session))
? rewriteOrcadMigrationPathValue(value, 'pty-session-id', drive)
: value

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

@nwparker
nwparker changed the base branch from nwparker/wsl-d1c-4a-unc-drive-scope to main October 29, 2026 01:00
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

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: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning 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 Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the WSL profile re-keying change for UNC Windows-drive projects converting to drive paths.
Description check Passed 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 Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

nwparker and others added 7 commits October 28, 2026 18:04
…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>
@OrcaWin
OrcaWin force-pushed the nwparker/wsl-d1c-4b-unc-rewrite branch from c3f6175 to 7ab29a6 Compare October 29, 2026 01:16
@OrcaWin

OrcaWin commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

STATUS: restacked after #33097 and #33108 merged. Nothing is merged by me.

Rebased onto main ccd0829e6f7. Main has since moved to 8b9d2770df5; update-branch covers that.

PR Head Base
#33128 7ab29a678a5 main
#33135 8e4c006b18a #33128
#33143 6cfca04dde6 #33135 (includes the IPC test fix from 4288890)
#33122 600a85617a9 #33143
#33531 982cbcdf6e3 #33122

Object.assign(next, { [field]: rewriteWslDriveUncValue(state[field], rewriter) })
}
}
next.projects = withWindowsGit(next.projects, repoIds, scope.distro)

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3


Review info
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: fd37e241-d523-42e6-8500-bc48fb83ff79
Commits

Reviewing files that changed from the base of the PR and between 8b9d277 and 7ab29a6.

Files selected for processing: 10
  • src/main/persistence/moving-wsl-projects/wsl-move-canonical-form.ts
  • src/main/ssh/orcad-migration-path-rewrite.ts
  • src/main/wsl/wsl-drive-unc-discovery-shadows.ts
  • src/main/wsl/wsl-drive-unc-identity-rewrite.test.ts
  • src/main/wsl/wsl-drive-unc-identity-rewrite.ts
  • src/main/wsl/wsl-drive-unc-project-scope.test.ts
  • src/main/wsl/wsl-drive-unc-project-scope.ts
  • src/main/wsl/wsl-drive-unc-state-rewrite.test.ts
  • src/main/wsl/wsl-drive-unc-state-rewrite.ts
  • src/shared/wsl-drive-unc-state-dispositions.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Comment on lines +146 to +147
const nests = (left: string, right: string): boolean =>
left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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.

Proposed fix
--- "a/src/main/wsl/wsl-drive-unc-project-scope.ts"
+++ "b/src/main/wsl/wsl-drive-unc-project-scope.ts"
@@ -143,8 +143,9 @@
       return drive ? [normalizeRuntimePathForComparison(drive)] : []
     })
   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 within = (path: string, root: string): boolean =>
+    path === root || path.startsWith(root.endsWith('/') ? root : `${root}/`)
+  const nests = (left: string, right: string): boolean => within(left, right) || within(right, left)
   const overlapping = new Set(
     repos
       .filter((repo) => scope.repoIds.includes(repo.id))
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.

Suggested change
const nests = (left: string, right: string): boolean =>
left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`)
const within = (path: string, root: string): boolean =>
path === root || path.startsWith(root.endsWith('/') ? root : `${root}/`)
const nests = (left: string, right: string): boolean => within(left, right) || within(right, left)

Comment on lines +148 to +159
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)
)
scope.repoIds = scope.repoIds.filter((id) => !overlapping.has(id))
for (const id of overlapping) {
scope.refused.push({ kind: 'repo', id, reason: 'overlaps-refused-project' })
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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.

Suggested change
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)
)
scope.repoIds = scope.repoIds.filter((id) => !overlapping.has(id))
for (const id of overlapping) {
scope.refused.push({ kind: 'repo', id, reason: 'overlaps-refused-project' })
}
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' })
}

Comment on lines +70 to +74
for (const [field, disposition] of Object.entries(WSL_DRIVE_UNC_STATE_DISPOSITIONS)) {
if (disposition === 'rewrite' && isStateField(field) && state[field] !== undefined) {
Object.assign(next, { [field]: rewriteWslDriveUncValue(state[field], rewriter) })
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor · Data Integrity & Integration · Quick win

Supported by static analysis

Script executed:

set -eu
printf '%s\n' '--- changed file ---'
nl -ba src/main/wsl/wsl-drive-unc-state-rewrite.ts
printf '%s\n' '--- references and tests ---'
rg -n -F --glob '*.ts' -e 'rewriteWslDriveUncState' -e 'barePath' -e 'repoIds' -e 'projectHostSetups' src/main/wsl src | head -240
printf '%s\n' '--- diff against requested base ---'
git diff --stat ccd0829e6f7293b0e0802ef7179271a9e13fe231 7ab29a678a52eae2317258dab9a27db559922d8b -- src/main/wsl/wsl-drive-unc-state-rewrite.ts
git diff --unified=30 ccd0829e6f7293b0e0802ef7179271a9e13fe231 7ab29a678a52eae2317258dab9a27db559922d8b -- src/main/wsl/wsl-drive-unc-state-rewrite.ts

Repository: stablyai/orca

Length of output: 40994


Script executed:

set -eu
printf '%s\n' '--- identity rewriter ---'
nl -ba src/main/wsl/wsl-drive-unc-identity-rewrite.ts
printf '%s\n' '--- state rewrite tests ---'
nl -ba src/main/wsl/wsl-drive-unc-state-rewrite.test.ts
printf '%s\n' '--- scope collector and move path helpers ---'
nl -ba src/main/wsl/wsl-drive-unc-project-scope.ts
nl -ba src/main/persistence/moving-wsl-projects/wsl-move-scope.ts

Repository: stablyai/orca

Length of output: 36985


Script executed:

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.

@nwparker
nwparker merged commit e1b7248 into main Oct 29, 2026
69 checks passed
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