fix(runtime): hide cli child windows by default

This commit is contained in:
iliya 2026-05-16 00:03:26 +03:00
parent d5f87a286a
commit 5610e13b98
2 changed files with 39 additions and 6 deletions

View file

@ -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<T extends { env?: NodeJS.ProcessEnv | Record<string, string | undefined> }>(
options: T
): T {
/** Apply shared CLI process defaults without overriding explicit caller choices. */
function withCliProcessDefaults<
T extends {
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
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<typeof spawn> {
const opts = withCliEnv(options);
const opts = withCliProcessDefaults(options);
const directLauncher = resolveDirectWindowsLauncher(binaryPath);
if (directLauncher) {
const directOpts = { ...opts };

View file

@ -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;