diff --git a/src/main/worktree-retirement-discovery-wsl.test.ts b/src/main/worktree-retirement-discovery-wsl.test.ts index 835309493d8..d44684f7ca7 100644 --- a/src/main/worktree-retirement-discovery-wsl.test.ts +++ b/src/main/worktree-retirement-discovery-wsl.test.ts @@ -109,6 +109,23 @@ describe('retirement discovery on WSL', () => { } }) + it('does not trust a UNC ENOENT, which is what a shut-down distro looks like', async () => { + // Windows maps an unreachable 9P route to ENOENT, so `wsl --shutdown` is indistinguishable + // from a distro that never held buckets. The home stays cached and still resolves, so nothing + // else marks the scan incomplete — believing ENOENT here memoizes the STA-4472 hole for good. + const enoent = Object.assign(new Error('ENOENT'), { code: 'ENOENT' }) + runWslTranscriptFsTaskMock.mockRejectedValue(enoent) + + const retired = await discoverRetiredWorktreeNames({ + workspaceRoots: [DISTRO_ROOT], + home: '/nonexistent-home', + env: {}, + resolveWslHome: async () => '\\\\wsl.localhost\\Ubuntu\\home\\ada' + }) + + expect(retired.complete).toBe(false) + }) + it('stays retryable when the distro home cannot be resolved, rather than memoizing the hole', async () => { // `getWslHomeAsync` shells out to `wsl.exe` and returns null for a stopped or slow distro. // Memoizing that as a complete "nothing is retired" is the STA-4472 defect itself, since the diff --git a/src/main/worktree-retirement-discovery.ts b/src/main/worktree-retirement-discovery.ts index 730a8bd2630..9b4aa2597fd 100644 --- a/src/main/worktree-retirement-discovery.ts +++ b/src/main/worktree-retirement-discovery.ts @@ -1,6 +1,6 @@ import { homedir } from 'node:os' import { join } from 'node:path' -import { parseWslUncPath } from '../shared/wsl-paths' +import { isWslUncPath, parseWslUncPath } from '../shared/wsl-paths' import { encodeClaudeProjectPaths, isClaudeProjectDirInScope @@ -68,8 +68,14 @@ async function readDirectoryNames(path: string): Promise<{ names: string[]; refu // A missing root is itself a complete answer: the agent may never have run on this machine. // Anything else — a gate refusal, an EIO on a redirected or network home — leaves names // unread, and memoizing that as "nothing is retired" is the under-retiring direction. + // + // Never on UNC though: Windows reports an unreachable 9P route as ENOENT, so a distro that is + // merely shut down is indistinguishable from one that never held buckets. `wsl.ts` already + // refuses to trust a UNC ENOENT for the same reason. Believing it here would memoize the + // STA-4472 hole for the process lifetime, since the cached distro home still resolves. const code = (error as NodeJS.ErrnoException | null)?.code - return { names: [], refused: code !== 'ENOENT' && code !== 'ENOTDIR' } + const absent = code === 'ENOENT' || code === 'ENOTDIR' + return { names: [], refused: !absent || isWslUncPath(path) } } }