diff --git a/packages/opencode/src/tool/external-directory.ts b/packages/opencode/src/tool/external-directory.ts index 2a7ba65df..3a9c7472f 100644 --- a/packages/opencode/src/tool/external-directory.ts +++ b/packages/opencode/src/tool/external-directory.ts @@ -55,7 +55,7 @@ function resolveWindowsForPermission(target: string, base: string, fs: Permissio if (!stat) throw Object.assign(new Error("missing"), { code: "ENOENT" }) current = stat.isSymbolicLink() ? fs.realpath(candidate) : candidate } catch (error: any) { - if (error?.code !== "ENOENT" && error?.code !== "ENOTDIR") throw error + if (!isMissingPermissionPath(error, candidate)) throw error return AppFileSystem.normalizePath(path.win32.join(current, part, ...parts.slice(i + 1)), { base }) } } @@ -85,6 +85,13 @@ function windowsPermissionPath(target: string, base: string): string { return uppercaseDriveRoot(`${base.replace(/[\\/]+$/, "")}\\${input}`) } +function isMissingPermissionPath(error: any, candidate: string): boolean { + if (error?.code === "ENOENT" || error?.code === "ENOTDIR") return true + // Bun on Windows reports unreachable UNC components as EUNKNOWN; for permission metadata + // resolution they are equivalent to a not-yet-existing path under the UNC share. + return error?.code === "EUNKNOWN" && error?.syscall === "lstat" && /^\\\\/.test(candidate) +} + function stripWindowsExtendedPrefix(target: string): string { if (/^\\\\\?\\UNC\\/i.test(target)) return target.replace(/^\\\\\?\\UNC\\/i, "\\\\") if (/^\\\\\?\\[A-Za-z]:\\/i.test(target)) return target.slice(4) diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index 07adfdd57..bbbd31efb 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -315,6 +315,29 @@ describe("tool.assertExternalDirectory", () => { }) }) + test("treats unreachable UNC components as missing paths for permission metadata", async () => { + await withWin32Platform(async () => { + const unreachable = "\\\\server\\share\\outside" + const resolved = resolveExternalPathForPermission("\\\\?\\UNC\\server\\share\\outside\\file.txt", "D:\\project", { + lstat: (candidate) => { + if (candidate.toLowerCase() === unreachable.toLowerCase()) { + throw Object.assign(new Error("unknown"), { + code: "EUNKNOWN", + path: candidate, + syscall: "lstat", + }) + } + return { + isSymbolicLink: () => false, + } as ReturnType + }, + realpath: (candidate) => candidate, + }) + + expect(resolved).toBe("\\\\server\\share\\outside\\file.txt") + }) + }) + if (process.platform === "win32") { test("normalizes Windows path variants to one glob", async () => { const { requests, ctx } = makeCtx()