From 1faba03cfebbde40b2866d9ef2cc40d0e9cdf3a8 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:36:22 -0500 Subject: [PATCH] fix(core): normalize Windows tool paths (#45806) --- packages/core/src/location-mutation.ts | 16 +++++++++------- packages/core/src/tool/plugin/shell.ts | 6 ++++-- packages/core/test/location-mutation.test.ts | 15 ++++++++++++--- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/core/src/location-mutation.ts b/packages/core/src/location-mutation.ts index 199d791db59..92f43f0e6f7 100644 --- a/packages/core/src/location-mutation.ts +++ b/packages/core/src/location-mutation.ts @@ -59,16 +59,18 @@ export interface Interface { readonly resolve: (input: ResolveInput) => Effect.Effect } -/** Lexical absolute path, expanding a leading `~` before resolving against `directory`. */ -export const resolvePath = (directory: string, input: string, home = Global.Path.home) => - path.resolve( +/** Lexical absolute path, normalizing Windows shell paths and expanding `~` before resolution. */ +export const resolvePath = (directory: string, input: string, home = Global.Path.home) => { + const normalized = FSUtil.windowsPath(input) + return path.resolve( directory, - input === "~" + normalized === "~" ? home - : input.startsWith("~/") || (process.platform === "win32" && input.startsWith("~\\")) - ? path.join(home, input.slice(2)) - : input, + : normalized.startsWith("~/") || (process.platform === "win32" && normalized.startsWith("~\\")) + ? path.join(home, normalized.slice(2)) + : normalized, ) +} export class Service extends Context.Service()("@opencode/LocationMutation") {} diff --git a/packages/core/src/tool/plugin/shell.ts b/packages/core/src/tool/plugin/shell.ts index 37d869039bc..7409b6acd43 100644 --- a/packages/core/src/tool/plugin/shell.ts +++ b/packages/core/src/tool/plugin/shell.ts @@ -1,6 +1,5 @@ export * as ShellTool from "./shell.js" -import path from "path" import { ToolFailure } from "@opencode-ai/ai" import type { Context as PluginContext } from "@opencode-ai/plugin/effect/plugin" import { Deferred, Effect, Schema, Scope } from "effect" @@ -190,7 +189,10 @@ export const Plugin = { portable, }) const directories = yield* Effect.forEach(parsed.directories, (directory) => - mutation.resolve({ path: path.resolve(target.absolute, directory), kind: "directory" }), + mutation.resolve({ + path: LocationMutation.resolvePath(target.absolute, directory), + kind: "directory", + }), ) const external = [target, ...directories] .map((item) => item.externalDirectory) diff --git a/packages/core/test/location-mutation.test.ts b/packages/core/test/location-mutation.test.ts index a768158a1ad..461190f750b 100644 --- a/packages/core/test/location-mutation.test.ts +++ b/packages/core/test/location-mutation.test.ts @@ -236,9 +236,18 @@ describe("LocationMutation", () => { expect(LocationMutation.resolvePath("/project", "~/notes.md", home)).toBe(path.resolve(home, "notes.md")) expect(LocationMutation.resolvePath("/project", "~draft.md", home)).toBe(path.resolve("/project", "~draft.md")) expect(LocationMutation.resolvePath("/project", "~\\notes.md", home)).toBe( - process.platform === "win32" - ? path.resolve(home, "notes.md") - : path.resolve("/project", "~\\notes.md"), + process.platform === "win32" ? path.resolve(home, "notes.md") : path.resolve("/project", "~\\notes.md"), + ) + }) + + test.each([ + ["/c/Users/aiden/notes.md", "C:/Users/aiden/notes.md"], + ["/C:/Users/aiden/notes.md", "C:/Users/aiden/notes.md"], + ["/cygdrive/c/Users/aiden/notes.md", "C:/Users/aiden/notes.md"], + ["/mnt/c/Users/aiden/notes.md", "C:/Users/aiden/notes.md"], + ])("normalizes Windows shell drive path %s before resolution", (input, windows) => { + expect(LocationMutation.resolvePath("/project", input)).toBe( + process.platform === "win32" ? path.resolve(windows) : path.resolve(input), ) })