diff --git a/packages/coding-agent/src/cli/daemon-command.ts b/packages/coding-agent/src/cli/daemon-command.ts index 9883707bd..2a49fa5b1 100644 --- a/packages/coding-agent/src/cli/daemon-command.ts +++ b/packages/coding-agent/src/cli/daemon-command.ts @@ -696,6 +696,7 @@ async function runStart(parsed: ParsedDaemonClientCommand): Promise { detached: true, env: process.env, stdio: "ignore", + windowsHide: true, }); child.unref(); diff --git a/packages/coding-agent/src/cli/daemon-launch.ts b/packages/coding-agent/src/cli/daemon-launch.ts index 6de9285e9..da6092973 100644 --- a/packages/coding-agent/src/cli/daemon-launch.ts +++ b/packages/coding-agent/src/cli/daemon-launch.ts @@ -379,6 +379,8 @@ async function ensureDaemonRunning(socketPath: string, spawnCwd?: string): Promi // (EPIPE once it exits); crash details come from the daemon log, // which the supervisor writes to before rethrowing startup errors. stdio: "ignore", + // detached on Windows would otherwise give the daemon a visible console. + windowsHide: true, }, ); let childFailure: diff --git a/packages/coding-agent/src/cli/daemon-update-restart.ts b/packages/coding-agent/src/cli/daemon-update-restart.ts index 7dc44ed57..4bde41fb1 100644 --- a/packages/coding-agent/src/cli/daemon-update-restart.ts +++ b/packages/coding-agent/src/cli/daemon-update-restart.ts @@ -552,6 +552,7 @@ export async function launchDaemonUpdateRestartCoordinator( detached: true, env: coordinatorEnvironment(agentDir), stdio: "ignore", + windowsHide: true, }); let launchError: Error | undefined; let exitDescription: string | undefined; diff --git a/packages/coding-agent/src/cli/owned-session-worker.ts b/packages/coding-agent/src/cli/owned-session-worker.ts index f7ab37535..ab8b8aa5e 100644 --- a/packages/coding-agent/src/cli/owned-session-worker.ts +++ b/packages/coding-agent/src/cli/owned-session-worker.ts @@ -356,6 +356,8 @@ export async function runOwnedSessionWorkerFrontend( [SESSION_LEASE_OWNER_ID_ENV]: `owned-${randomUUID()}`, }, stdio, + // Console-less parents would otherwise flash a console window on Windows. + windowsHide: true, }); currentChild = child; if (!interactive) { diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts index b709ab10e..cbf545ca5 100644 --- a/packages/coding-agent/src/config.ts +++ b/packages/coding-agent/src/config.ts @@ -210,6 +210,7 @@ function readCommandOutput( encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], shell: shouldUseWindowsShell(command), + windowsHide: true, }); if (result.status === 0) return result.stdout.trim() || undefined; if (options.requireSuccess) { diff --git a/packages/coding-agent/src/core/autonomous.ts b/packages/coding-agent/src/core/autonomous.ts index 75930d415..84c7e6671 100644 --- a/packages/coding-agent/src/core/autonomous.ts +++ b/packages/coding-agent/src/core/autonomous.ts @@ -496,6 +496,7 @@ function runChildProcess( detached: process.platform !== "win32", shell: options.shell === true, stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, }); if (child.pid) { trackDetachedChildPid(child.pid); diff --git a/packages/coding-agent/src/core/footer-data-provider.ts b/packages/coding-agent/src/core/footer-data-provider.ts index 2aff8ac32..291091879 100644 --- a/packages/coding-agent/src/core/footer-data-provider.ts +++ b/packages/coding-agent/src/core/footer-data-provider.ts @@ -10,6 +10,7 @@ function resolveBranchWithGitSync(repoDir: string): string | null { cwd: repoDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], + windowsHide: true, }); const branch = result.status === 0 ? result.stdout.trim() : ""; return branch || null; @@ -24,6 +25,7 @@ function resolveBranchWithGitAsync(repoDir: string): Promise { { cwd: repoDir, encoding: "utf8", + windowsHide: true, }, (error: ExecFileException | null, stdout: string) => { if (error) { diff --git a/packages/coding-agent/src/core/package-manager.ts b/packages/coding-agent/src/core/package-manager.ts index fd818595f..4fcd19587 100644 --- a/packages/coding-agent/src/core/package-manager.ts +++ b/packages/coding-agent/src/core/package-manager.ts @@ -2383,6 +2383,7 @@ export class DefaultPackageManager implements PackageManager { stdio: isStdoutTakenOver() ? ["ignore", 2, 2] : "inherit", shell: shouldUseWindowsShell(command), env: getEnv(), + windowsHide: true, }); } @@ -2397,6 +2398,7 @@ export class DefaultPackageManager implements PackageManager { stdio: ["ignore", "pipe", "pipe"], shell: shouldUseWindowsShell(command), env: options?.env ? { ...baseEnv, ...options.env } : baseEnv, + windowsHide: true, }); } @@ -2464,6 +2466,7 @@ export class DefaultPackageManager implements PackageManager { encoding: "utf-8", shell: shouldUseWindowsShell(command), env: getEnv(), + windowsHide: true, }); if (result.error || result.status !== 0) { throw new Error( diff --git a/packages/coding-agent/src/core/resolve-config-value.ts b/packages/coding-agent/src/core/resolve-config-value.ts index 646042e1c..be47128c0 100644 --- a/packages/coding-agent/src/core/resolve-config-value.ts +++ b/packages/coding-agent/src/core/resolve-config-value.ts @@ -58,6 +58,7 @@ function executeWithDefaultShell(command: string): string | undefined { encoding: "utf-8", timeout: 10000, stdio: ["ignore", "pipe", "ignore"], + windowsHide: true, }); return output.trim() || undefined; } catch { diff --git a/packages/coding-agent/src/core/session-file-actions.ts b/packages/coding-agent/src/core/session-file-actions.ts index adcec4c01..94e706d57 100644 --- a/packages/coding-agent/src/core/session-file-actions.ts +++ b/packages/coding-agent/src/core/session-file-actions.ts @@ -25,7 +25,7 @@ async function deleteSessionArtifacts(sessionPath: string): Promise { /** Remove the session `.jsonl`, trying the `trash` CLI first, then falling back to unlink. */ async function removeSessionFile(sessionPath: string): Promise { const trashArgs = sessionPath.startsWith("-") ? ["--", sessionPath] : [sessionPath]; - const trashResult = spawnSync("trash", trashArgs, { encoding: "utf-8" }); + const trashResult = spawnSync("trash", trashArgs, { encoding: "utf-8", windowsHide: true }); const getTrashErrorHint = (): string | null => { const parts: string[] = []; diff --git a/packages/coding-agent/src/core/tools/bash.ts b/packages/coding-agent/src/core/tools/bash.ts index efc260eae..e8ca48dbf 100644 --- a/packages/coding-agent/src/core/tools/bash.ts +++ b/packages/coding-agent/src/core/tools/bash.ts @@ -77,6 +77,9 @@ export function createLocalBashOperations(options?: { shellPath?: string }): Bas detached: process.platform !== "win32", env: env ?? getShellEnv(), stdio: ["ignore", "pipe", "pipe"], + // Console-less parents (daemon/workers) would otherwise flash a + // console window per bash call on Windows. + windowsHide: true, }); if (child.pid) trackDetachedChildPid(child.pid); let timedOut = false; diff --git a/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts b/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts index c841b5b79..ba7435cc8 100644 --- a/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts +++ b/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts @@ -396,6 +396,8 @@ export class DaemonCatalogClient { cwd: process.cwd(), env: createCliSubprocessEnv({ ...process.env, [DAEMON_CATALOG_ROLE_ENV]: "1" }), stdio: ["ignore", "ignore", "ignore", "ipc"], + // The daemon has no console; without this Windows pops one for the child. + windowsHide: true, }); this.child = child; child.on("message", (value: unknown) => this.handleMessage(value)); diff --git a/packages/coding-agent/src/modes/daemon/daemon-mode.ts b/packages/coding-agent/src/modes/daemon/daemon-mode.ts index dfebcdf61..f8d6feb1c 100644 --- a/packages/coding-agent/src/modes/daemon/daemon-mode.ts +++ b/packages/coding-agent/src/modes/daemon/daemon-mode.ts @@ -820,6 +820,7 @@ export class AgentDaemon { detached: true, env: environment, stdio: "ignore", + windowsHide: true, }); child.unref(); const deadline = Date.now() + 10_000; diff --git a/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts b/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts index b22701bbc..3fc82b42b 100644 --- a/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts +++ b/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts @@ -2131,6 +2131,9 @@ export class DaemonSupervisor { [SESSION_LEASE_OWNER_ID_ENV]: rootActiveSessionId, }), stdio: ["ignore", "ignore", "pipe", "pipe"], + // detached on Windows gives the worker its own visible console window + // (the ipython kernel then attaches to it, titled as python) — hide it. + windowsHide: true, }); const detachWorkerStderr = child.stderr ? attachJsonlLineReader(child.stderr, (line) => this.log(`Session worker ${workerId} stderr: ${line}`), { @@ -4864,6 +4867,7 @@ export class DaemonSupervisor { detached: true, env: environment, stdio: "ignore", + windowsHide: true, }); replacement.unref(); } diff --git a/packages/coding-agent/src/modes/rpc/rpc-client.ts b/packages/coding-agent/src/modes/rpc/rpc-client.ts index 7c3278816..e9b074d25 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-client.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-client.ts @@ -107,6 +107,7 @@ export class RpcClient { cwd: this.options.cwd, env: { ...process.env, ...this.options.env }, stdio: ["pipe", "pipe", "pipe"], + windowsHide: true, }); // Collect stderr for debugging diff --git a/packages/coding-agent/src/package-manager-cli.ts b/packages/coding-agent/src/package-manager-cli.ts index a9f4bd6cb..26e7886f5 100644 --- a/packages/coding-agent/src/package-manager-cli.ts +++ b/packages/coding-agent/src/package-manager-cli.ts @@ -466,6 +466,7 @@ async function runSelfUpdate(command: SelfUpdateCommand): Promise { const child = spawn(step.command, step.args, { stdio: "inherit", shell: shouldUseWindowsShell(step.command), + windowsHide: true, }); child.on("error", (error) => { reject(error); diff --git a/packages/coding-agent/src/utils/clipboard-image.ts b/packages/coding-agent/src/utils/clipboard-image.ts index 4cf44908f..84383dc61 100644 --- a/packages/coding-agent/src/utils/clipboard-image.ts +++ b/packages/coding-agent/src/utils/clipboard-image.ts @@ -98,6 +98,7 @@ function runCommand( timeout: timeoutMs, maxBuffer: maxBufferBytes, env: options?.env, + windowsHide: true, }); if (result.error) { diff --git a/packages/coding-agent/src/utils/clipboard.ts b/packages/coding-agent/src/utils/clipboard.ts index 84166e95e..4354a7738 100644 --- a/packages/coding-agent/src/utils/clipboard.ts +++ b/packages/coding-agent/src/utils/clipboard.ts @@ -7,6 +7,7 @@ type NativeClipboardExecOptions = { input: string; timeout: number; stdio: ["pipe", "ignore", "ignore"]; + windowsHide: boolean; }; function copyToX11Clipboard(options: NativeClipboardExecOptions): void { @@ -61,7 +62,12 @@ export async function copyToClipboard(text: string): Promise { return; } - const options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: ["pipe", "ignore", "ignore"] }; + const options: NativeClipboardExecOptions = { + input: text, + timeout: 5000, + stdio: ["pipe", "ignore", "ignore"], + windowsHide: true, + }; if (!copied) { try { diff --git a/packages/coding-agent/src/utils/git.ts b/packages/coding-agent/src/utils/git.ts index b60d98a00..b4bf29fbb 100644 --- a/packages/coding-agent/src/utils/git.ts +++ b/packages/coding-agent/src/utils/git.ts @@ -253,6 +253,7 @@ function runGit(cwd: string, args: string[]): string | null { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], + windowsHide: true, }); if (result.status !== 0 || typeof result.stdout !== "string") return null; return result.stdout.trim() || null; diff --git a/packages/coding-agent/src/utils/shell.ts b/packages/coding-agent/src/utils/shell.ts index cbf289d86..6d60fb941 100644 --- a/packages/coding-agent/src/utils/shell.ts +++ b/packages/coding-agent/src/utils/shell.ts @@ -16,7 +16,7 @@ function findBashOnPath(): string | null { if (process.platform === "win32") { // Windows: Use 'where' and verify file exists (where can return non-existent paths) try { - const result = spawnSync("where", ["bash.exe"], { encoding: "utf-8", timeout: 5000 }); + const result = spawnSync("where", ["bash.exe"], { encoding: "utf-8", timeout: 5000, windowsHide: true }); if (result.status === 0 && result.stdout) { const firstMatch = result.stdout.trim().split(/\r?\n/)[0]; if (firstMatch && existsSync(firstMatch)) { @@ -31,7 +31,7 @@ function findBashOnPath(): string | null { // Unix: Use 'which' and trust its output (handles Termux and special filesystems) try { - const result = spawnSync("which", ["bash"], { encoding: "utf-8", timeout: 5000 }); + const result = spawnSync("which", ["bash"], { encoding: "utf-8", timeout: 5000, windowsHide: true }); if (result.status === 0 && result.stdout) { const firstMatch = result.stdout.trim().split(/\r?\n/)[0]; if (firstMatch) { @@ -194,6 +194,7 @@ export function killProcessTree(pid: number): void { spawn("taskkill", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", detached: true, + windowsHide: true, }); } catch { // Ignore errors if taskkill fails diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-agent/src/utils/tools-manager.ts index c3da7045e..289cc220b 100644 --- a/packages/coding-agent/src/utils/tools-manager.ts +++ b/packages/coding-agent/src/utils/tools-manager.ts @@ -100,7 +100,7 @@ const TOOLS: Record = { // Check that a command both launches and reports a successful version. function commandWorks(cmd: string): boolean { try { - const result = spawnSync(cmd, ["--version"], { stdio: "pipe", timeout: COMMAND_TIMEOUT_MS }); + const result = spawnSync(cmd, ["--version"], { stdio: "pipe", timeout: COMMAND_TIMEOUT_MS, windowsHide: true }); return !result.error && result.status === 0; } catch { return false; @@ -224,7 +224,10 @@ async function downloadTool(tool: ManagedTool): Promise { try { if (assetName.endsWith(".tar.gz")) { - const extractResult = spawnSync("tar", ["xzf", archivePath, "-C", extractDir], { stdio: "pipe" }); + const extractResult = spawnSync("tar", ["xzf", archivePath, "-C", extractDir], { + stdio: "pipe", + windowsHide: true, + }); if (extractResult.error || extractResult.status !== 0) { const errMsg = extractResult.error?.message ?? extractResult.stderr?.toString().trim() ?? "unknown error"; throw new Error(`Failed to extract ${assetName}: ${errMsg}`);