mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
fix(agent-core): hide console window when running hooks on Windows (#1466)
Pass windowsHide:true when spawning the hook process so a visible console no longer flashes and steals focus on Windows. The Bash-tool path was already hardened (KAOS buildLocalSpawnOptions); the hook runner missed the flag even though its own taskkill helper already set it. Extract the spawn options into a pure builder and add a regression test asserting windowsHide, mirroring the existing KAOS spawn-options test. Relates to #1298.
This commit is contained in:
parent
08b3c3fc53
commit
063bce2a2f
3 changed files with 51 additions and 8 deletions
5
.changeset/fix-hooks-windows-console-popup.md
Normal file
5
.changeset/fix-hooks-windows-console-popup.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@moonshot-ai/kimi-code": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix console windows flashing on Windows each time a hook runs.
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
|
import { spawn, type ChildProcessWithoutNullStreams, type SpawnOptionsWithoutStdio } from 'node:child_process';
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
|
@ -11,6 +11,24 @@ export interface RunHookOptions {
|
||||||
readonly signal?: AbortSignal;
|
readonly signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildHookSpawnOptions(options: {
|
||||||
|
cwd?: string;
|
||||||
|
env?: Readonly<Record<string, string>>;
|
||||||
|
}): SpawnOptionsWithoutStdio {
|
||||||
|
return {
|
||||||
|
shell: true,
|
||||||
|
cwd: options.cwd,
|
||||||
|
stdio: 'pipe',
|
||||||
|
detached: process.platform !== 'win32',
|
||||||
|
// Hide the console Windows would otherwise allocate for the shell child.
|
||||||
|
// Without `windowsHide:true`, each hook flashes a visible console window —
|
||||||
|
// the same regression the Bash tool path already guards against in KAOS
|
||||||
|
// (see `buildLocalSpawnOptions`). Unconditional: it is a no-op on POSIX.
|
||||||
|
windowsHide: true,
|
||||||
|
env: options.env ? { ...process.env, ...options.env } : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT_SECONDS = 30;
|
const DEFAULT_TIMEOUT_SECONDS = 30;
|
||||||
const KILL_GRACE_MS = 100;
|
const KILL_GRACE_MS = 100;
|
||||||
const OptionalStringSchema = z.preprocess(
|
const OptionalStringSchema = z.preprocess(
|
||||||
|
|
@ -46,13 +64,7 @@ export async function runHook(
|
||||||
): Promise<HookResult> {
|
): Promise<HookResult> {
|
||||||
let child: ChildProcessWithoutNullStreams;
|
let child: ChildProcessWithoutNullStreams;
|
||||||
try {
|
try {
|
||||||
child = spawn(command, {
|
child = spawn(command, buildHookSpawnOptions({ cwd: options.cwd, env: options.env }));
|
||||||
shell: true,
|
|
||||||
cwd: options.cwd,
|
|
||||||
stdio: 'pipe',
|
|
||||||
detached: process.platform !== 'win32',
|
|
||||||
env: options.env ? { ...process.env, ...options.env } : undefined,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return allowResult({ stderr: errorMessage(error) });
|
return allowResult({ stderr: errorMessage(error) });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { buildHookSpawnOptions } from '../../src/session/hooks/runner';
|
||||||
|
|
||||||
const RUNNER_MODULE = '../../src/session/hooks/runner' as string;
|
const RUNNER_MODULE = '../../src/session/hooks/runner' as string;
|
||||||
|
|
||||||
interface HookResult {
|
interface HookResult {
|
||||||
|
|
@ -98,3 +100,27 @@ describe('runHook process runner', () => {
|
||||||
expect(result.stdout?.trim()).toBe('WriteFile');
|
expect(result.stdout?.trim()).toBe('WriteFile');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression coverage for the "every hook flashes an empty console window on
|
||||||
|
// Windows" bug. With `shell:true` and no `windowsHide`, Node allocates a
|
||||||
|
// visible console for each hook child process on Windows. The fix is to pass
|
||||||
|
// `windowsHide:true` (mirrors KAOS' `buildLocalSpawnOptions` and the runner's
|
||||||
|
// own taskkill spawn). The flag is only observable on Windows, so we assert
|
||||||
|
// the spawn options builder directly.
|
||||||
|
describe('buildHookSpawnOptions (Windows console-window regression)', () => {
|
||||||
|
it('sets windowsHide:true so hooks do not flash a console on Windows', () => {
|
||||||
|
expect(buildHookSpawnOptions({}).windowsHide).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs through the shell with stdio piped', () => {
|
||||||
|
const options = buildHookSpawnOptions({});
|
||||||
|
expect(options.shell).toBe(true);
|
||||||
|
expect(options.stdio).toBe('pipe');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('merges hook env onto process.env and forwards cwd', () => {
|
||||||
|
const options = buildHookSpawnOptions({ cwd: '/repo', env: { FOO: 'bar' } });
|
||||||
|
expect(options.cwd).toBe('/repo');
|
||||||
|
expect(options.env).toMatchObject({ FOO: 'bar' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue