From c15922f2004ae28df9ae7e3bd26822d8dd9b4ffd Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 21 Aug 2026 04:28:17 +0000 Subject: [PATCH] fix(core): bypass Windows Git lookup Co-authored-by: Hona <10430890+Hona@users.noreply.github.com> --- packages/core/src/git.ts | 22 +++++++++++++++------- packages/util/src/cross-spawn-spawner.ts | 10 +++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/core/src/git.ts b/packages/core/src/git.ts index aedcade5bf1..691adafb1a7 100644 --- a/packages/core/src/git.ts +++ b/packages/core/src/git.ts @@ -9,6 +9,10 @@ import { AppProcess } from "@opencode-ai/util/process" import { makeGlobalNode } from "@opencode-ai/util/effect/app-node" import { File } from "./file.js" import { KeyedMutex } from "./effect/keyed-mutex.js" +import { which } from "./util/which.js" + +const resolvedGit = process.platform === "win32" ? which("git") : undefined +const gitExecutable = resolvedGit ? path.resolve(resolvedGit) : "git" export class Repository extends Schema.Class("Git.Repository")({ worktree: AbsolutePath, @@ -314,7 +318,7 @@ const layer = Layer.effect( ) { const result = yield* proc .run( - ChildProcess.make("git", repositoryArgs(repository, args), { + ChildProcess.make(gitExecutable, repositoryArgs(repository, args), { cwd: repository.worktree, env: options?.env, extendEnv: true, @@ -485,10 +489,14 @@ const layer = Layer.effect( if (!input.paths.length) return new Set() const result = yield* proc .run( - ChildProcess.make("git", repositoryArgs(input.repository, ["check-ignore", "--no-index", "--stdin", "-z"]), { - cwd: input.repository.worktree, - extendEnv: true, - }), + ChildProcess.make( + gitExecutable, + repositoryArgs(input.repository, ["check-ignore", "--no-index", "--stdin", "-z"]), + { + cwd: input.repository.worktree, + extendEnv: true, + }, + ), { stdin: input.paths.join("\0") + "\0" }, ) .pipe( @@ -662,7 +670,7 @@ const layer = Layer.effect( cwd = repository.worktree, ) { const result = yield* proc - .run(ChildProcess.make("git", args, { cwd, extendEnv: true, stdin: "ignore" })) + .run(ChildProcess.make(gitExecutable, args, { cwd, extendEnv: true, stdin: "ignore" })) .pipe( Effect.mapError( (cause) => new WorktreeError({ operation, directory: worktreeDirectory, message: cause.message, cause }), @@ -759,7 +767,7 @@ function execute(cwd: string, proc: AppProcess.Interface) { return (args: string[]) => proc .run( - ChildProcess.make("git", args, { + ChildProcess.make(gitExecutable, args, { cwd, extendEnv: true, stdin: "ignore", diff --git a/packages/util/src/cross-spawn-spawner.ts b/packages/util/src/cross-spawn-spawner.ts index 6a8a412b953..3826a3eaea8 100644 --- a/packages/util/src/cross-spawn-spawner.ts +++ b/packages/util/src/cross-spawn-spawner.ts @@ -22,6 +22,7 @@ import { makeGlobalNode } from "./effect/app-node.js" import { filesystem, path } from "./effect/app-node-platform.js" const toError = (err: unknown): Error => (err instanceof globalThis.Error ? err : new globalThis.Error(String(err))) +const nativeWindowsExtensions = new Set([".com", ".exe"]) const toTag = (err: NodeJS.ErrnoException): PlatformError.SystemErrorTag => { switch (err.code) { @@ -261,7 +262,14 @@ const makeCrossSpawnSpawner = Effect.gen(function* () { const launchProcess = (command: ChildProcess.StandardCommand, opts: NodeChildProcess.SpawnOptions) => Effect.callback((resume) => { const signal = Deferred.makeUnsafe() - const proc = launch(command.command, command.args, opts) + const native = + process.platform === "win32" && + !opts.shell && + path.isAbsolute(command.command) && + nativeWindowsExtensions.has(path.extname(command.command).toLowerCase()) + const proc = native + ? NodeChildProcess.spawn(command.command, command.args, opts) + : launch(command.command, command.args, opts) let end = false let exit: readonly [code: number | null, signal: NodeJS.Signals | null] | undefined proc.on("error", (err) => {