From 5610e13b9817d6978e4bd89a658431607f9c3310 Mon Sep 17 00:00:00 2001 From: iliya Date: Sat, 16 May 2026 00:03:26 +0300 Subject: [PATCH] fix(runtime): hide cli child windows by default --- src/main/utils/childProcess.ts | 16 +++++++++------ test/main/utils/childProcess.test.ts | 29 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/main/utils/childProcess.ts b/src/main/utils/childProcess.ts index 6bc72d94..7305f824 100644 --- a/src/main/utils/childProcess.ts +++ b/src/main/utils/childProcess.ts @@ -242,12 +242,16 @@ export function killTrackedCliProcesses(signal: NodeJS.Signals = 'SIGKILL'): voi } } -/** Merge CLI_ENV_DEFAULTS into spawn/exec options.env (or process.env if absent). */ -function withCliEnv }>( - options: T -): T { +/** Apply shared CLI process defaults without overriding explicit caller choices. */ +function withCliProcessDefaults< + T extends { + env?: NodeJS.ProcessEnv | Record; + windowsHide?: boolean; + }, +>(options: T): T & { windowsHide: boolean } { return { ...options, + windowsHide: options.windowsHide ?? true, env: { ...(options.env ?? process.env), ...CLI_ENV_DEFAULTS }, }; } @@ -270,7 +274,7 @@ export async function execCli( ); } const target = binaryPath; - const opts = withCliEnv(options); + const opts = withCliProcessDefaults(options); const directLauncher = resolveDirectWindowsLauncher(target); if (directLauncher) { const result = await execFileAsync( @@ -316,7 +320,7 @@ export function spawnCli( args: string[], options: SpawnOptions = {} ): ReturnType { - const opts = withCliEnv(options); + const opts = withCliProcessDefaults(options); const directLauncher = resolveDirectWindowsLauncher(binaryPath); if (directLauncher) { const directOpts = { ...opts }; diff --git a/test/main/utils/childProcess.test.ts b/test/main/utils/childProcess.test.ts index 14f5982c..e444d7fa 100644 --- a/test/main/utils/childProcess.test.ts +++ b/test/main/utils/childProcess.test.ts @@ -134,6 +134,18 @@ describe('cli child process helpers', () => { expect(result).toEqual({} as any); }); + it('hides spawned CLI windows by default but preserves explicit opt-out', () => { + setPlatform('win32'); + const spawnMock = child.spawn as unknown as Mock; + spawnMock.mockReturnValue({} as any); + + spawnCli('C:\\bin\\claude.exe', ['--version']); + expect(spawnMock.mock.calls[0][2]).toMatchObject({ windowsHide: true }); + + spawnCli('C:\\bin\\claude.exe', ['--version'], { windowsHide: false }); + expect(spawnMock.mock.calls[1][2]).toMatchObject({ windowsHide: false }); + }); + it('falls back to shell when spawn throws EINVAL', () => { setPlatform('win32'); const error: any = new Error('spawn EINVAL'); @@ -296,6 +308,23 @@ describe('cli child process helpers', () => { expect(result.stdout).toBe('ok'); }); + it('hides exec CLI windows by default but preserves explicit opt-out', async () => { + setPlatform('win32'); + const execFileMock = child.execFile as unknown as Mock; + execFileMock.mockImplementation( + (_cmd: string, _args: string[], _opts: unknown, cb: ExecCallback) => { + cb(null, 'ok', ''); + return {} as any; + } + ); + + await execCli('C:\\bin\\claude.exe', ['--version']); + expect(execFileMock.mock.calls[0][2]).toMatchObject({ windowsHide: true }); + + await execCli('C:\\bin\\claude.exe', ['--version'], { windowsHide: false }); + expect(execFileMock.mock.calls[1][2]).toMatchObject({ windowsHide: false }); + }); + it('skips straight to shell for Windows cmd launchers', async () => { setPlatform('win32'); const execFileMock = child.execFile as unknown as Mock;