From 3b7971a463bd01196cf6cefa5f859e72950d85fc Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 10 May 2026 19:46:25 +0800 Subject: [PATCH] Fix Windows git command console flashing (#897) --- packages/desktop/src/login-shell-env.ts | 1 + packages/desktop/src/main.ts | 2 + .../server/agent/provider-launch-config.ts | 1 + packages/server/src/utils/run-git-command.ts | 1 + .../run-git-command.windows-shell.test.ts | 42 +++++++++++++++++++ 5 files changed, 47 insertions(+) create mode 100644 packages/server/src/utils/run-git-command.windows-shell.test.ts diff --git a/packages/desktop/src/login-shell-env.ts b/packages/desktop/src/login-shell-env.ts index a7f92f734..62c0968e3 100644 --- a/packages/desktop/src/login-shell-env.ts +++ b/packages/desktop/src/login-shell-env.ts @@ -62,6 +62,7 @@ function resolveShellEnv(): Record | undefined { const result = spawnSync(shell, [...shellArgs, command], { encoding: "utf8", timeout: RESOLVE_TIMEOUT_MS, + windowsHide: true, env: { ...shellEnv, ELECTRON_RUN_AS_NODE: "1", diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts index 009b00fd8..5efd7a534 100644 --- a/packages/desktop/src/main.ts +++ b/packages/desktop/src/main.ts @@ -191,6 +191,7 @@ if (forcedUserDataDir) { const topLevel = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8", timeout: 3000, + windowsHide: true, }).trim(); devWorktreeName = path.basename(topLevel); // Main checkout (e.g. "paseo") gets default userData — only worktrees diverge. @@ -200,6 +201,7 @@ if (forcedUserDataDir) { cwd: topLevel, encoding: "utf-8", timeout: 3000, + windowsHide: true, }).trim(), ); const isWorktree = path.resolve(topLevel, ".git") !== commonDir; diff --git a/packages/server/src/server/agent/provider-launch-config.ts b/packages/server/src/server/agent/provider-launch-config.ts index 617e56bf0..26eb093a2 100644 --- a/packages/server/src/server/agent/provider-launch-config.ts +++ b/packages/server/src/server/agent/provider-launch-config.ts @@ -230,6 +230,7 @@ export function findExecutable(name: string): string | null { const result = execFileSync(cmd, [trimmed], { encoding: "utf8", env: createProviderEnv({ baseEnv: process.env }), + windowsHide: true, }).trim(); const lines = result.split(/\r?\n/).filter((l: string) => l.trim()); const candidate = lines.at(-1)?.trim() ?? null; diff --git a/packages/server/src/utils/run-git-command.ts b/packages/server/src/utils/run-git-command.ts index 43cf48d9c..8e1fc59c3 100644 --- a/packages/server/src/utils/run-git-command.ts +++ b/packages/server/src/utils/run-git-command.ts @@ -54,6 +54,7 @@ export function runGitCommand( const child = spawnProcess("git", args, { cwd: options.cwd, envOverlay: mergeEnvOverlays(options.env, options.envOverlay), + shell: false, stdio: ["ignore", "pipe", "pipe"], }); diff --git a/packages/server/src/utils/run-git-command.windows-shell.test.ts b/packages/server/src/utils/run-git-command.windows-shell.test.ts new file mode 100644 index 000000000..ed7980c55 --- /dev/null +++ b/packages/server/src/utils/run-git-command.windows-shell.test.ts @@ -0,0 +1,42 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +import { runGitCommand } from "./run-git-command.js"; + +const tempDirs: string[] = []; + +function makeTempRepo(): string { + const repo = mkdtempSync(path.join(tmpdir(), "paseo-git-shell-")); + tempDirs.push(repo); + return repo; +} + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +}); + +describe("runGitCommand shell behavior", () => { + it("passes git arguments directly instead of through the platform shell", async () => { + const repo = makeTempRepo(); + const literalName = "%PASEO_GIT_SHELL_SENTINEL%"; + const expandedName = "expanded-by-cmd"; + + await runGitCommand(["init"], { cwd: repo }); + writeFileSync(path.join(repo, literalName), "literal\n"); + writeFileSync(path.join(repo, expandedName), "expanded\n"); + await runGitCommand(["add", literalName, expandedName], { cwd: repo }); + + const result = await runGitCommand(["ls-files", "--error-unmatch", literalName], { + cwd: repo, + envOverlay: { + PASEO_GIT_SHELL_SENTINEL: expandedName, + }, + }); + + expect(result.stdout.trim()).toBe(literalName); + }); +});