From 2ca2b008fcd87b4a706d78a5e3f811366fda727c Mon Sep 17 00:00:00 2001 From: Neil Date: Thu, 10 Sep 2026 17:25:03 -0700 Subject: [PATCH] fix(worktree): recognise the Windows profile through WSL's drvfs view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/mnt/` under a WSL UNC alias is the distro's drvfs mount of a Windows volume, so `\\wsl.localhost\Ubuntu\mnt\c\Users\bob` is `C:\Users\bob` wearing a Linux spelling. The Windows-profile rule excludes every WSL UNC path by design (the aliases normally front a Linux filesystem) and the POSIX shapes never match a `/mnt/...` tail, so that path fell through both and read back as deletable. The spelling is producible by the product: `resolveWslRepoWorktreeBasePath` maps a `/mnt/c/...` worktree base against a WSL repo into exactly this UNC form, and `getWslFilesystemBoundaryDistro` already treats it as the drvfs crossing. A drvfs tail now takes the Windows rule on its drive form, via the existing `toWindowsWslDrivePath`. Scoped to the UNC branch, where `parseWslUncPath` has proven the path is a WSL alias — a plain Linux host's `/mnt/c/...` is untouched. The lowercase-only `/mnt` match is deliberate: `/MNT` is an ordinary case-sensitive Linux directory, never the automount. --- src/main/worktree-removal-home-guard.test.ts | 14 ++++++++++++++ src/main/worktree-removal-home-guard.ts | 17 +++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/worktree-removal-home-guard.test.ts b/src/main/worktree-removal-home-guard.test.ts index d06940acdd3..14ab1ac674f 100644 --- a/src/main/worktree-removal-home-guard.test.ts +++ b/src/main/worktree-removal-home-guard.test.ts @@ -87,6 +87,20 @@ describe('path-shape home detection', () => { ])('WSL UNC %s -> %s', (worktreePath, expected) => { expect(isHome(worktreePath, CLIENT_REMOVAL_HOME)).toBe(expected) }) + + it.each([ + // `/mnt/` is the distro's drvfs view of a Windows volume, so this is + // `C:\Users\bob` wearing a Linux spelling, not a directory in the distro. + ['\\\\wsl.localhost\\Ubuntu\\mnt\\c\\Users\\bob', true], + ['\\\\wsl$\\Ubuntu\\mnt\\c\\Users', true], + ['\\\\wsl.localhost\\Ubuntu\\mnt\\c\\Users\\bob\\ws\\wt', false], + // `/MNT` is an ordinary case-sensitive Linux directory, never the automount. + ['\\\\wsl.localhost\\Ubuntu\\MNT\\c\\Users\\bob', false] + ])('WSL drvfs %s -> %s', (worktreePath, expected) => { + expect(withProcessPlatform('darwin', () => isHome(worktreePath, CLIENT_REMOVAL_HOME))).toBe( + expected + ) + }) }) describe('whose home the guard consults', () => { diff --git a/src/main/worktree-removal-home-guard.ts b/src/main/worktree-removal-home-guard.ts index 004be97d6dc..c8466610cc8 100644 --- a/src/main/worktree-removal-home-guard.ts +++ b/src/main/worktree-removal-home-guard.ts @@ -17,7 +17,7 @@ import { homedir } from 'node:os' import { posix, win32 } from 'node:path' import { isWindowsAbsolutePathLike } from '../shared/cross-platform-path' -import { parseWslUncPath } from '../shared/wsl-paths' +import { parseWslUncPath, toWindowsWslDrivePath } from '../shared/wsl-paths' export type PathOps = typeof posix @@ -172,13 +172,26 @@ function isLikelyWindowsUserProfileDirectory( /** * WSL UNC aliases front a Linux filesystem, so POSIX home shapes — not * `\Users` — are what protect `\\wsl.localhost\Ubuntu\home\alice`. + * + * Except under `/mnt/`: that tail is the distro's drvfs view of a Windows + * volume, so `\\wsl.localhost\Ubuntu\mnt\c\Users\bob` is the Windows profile with + * a Linux spelling. It takes the Windows rule on its drive form, which the UNC + * exclusion above would otherwise skip. */ function isLikelyWslDistroHomeDirectory(resolvedWorktreePath: string, pathOps: PathOps): boolean { if (pathOps !== win32) { return false } const wsl = parseWslUncPath(resolvedWorktreePath) - return !!wsl && (wsl.linuxPath === '/' || isPosixHomeRoot(trimTrailingSlash(wsl.linuxPath))) + if (!wsl) { + return false + } + const linuxPath = trimTrailingSlash(wsl.linuxPath) + const drivePath = toWindowsWslDrivePath(linuxPath) + if (drivePath) { + return isLikelyWindowsUserProfileDirectory(win32.resolve(drivePath), win32) + } + return wsl.linuxPath === '/' || isPosixHomeRoot(linuxPath) } function isWslUncRemovalPath(resolvedWorktreePath: string): boolean {