fix(gateway): prevent reconnect race and globally hide windows subprocess console

This commit is contained in:
Haze
2026-03-02 19:40:16 +08:00
parent dfdbde374d
commit 3596464803
7 changed files with 15 additions and 25 deletions

View File

@@ -106,6 +106,7 @@ export class ClawHubService {
...env,
CLAWHUB_WORKDIR: this.workDir,
},
windowsHide: true,
});
let stdout = '';

View File

@@ -39,7 +39,6 @@ import {
getReconnectSkipReason,
isLifecycleSuperseded,
nextLifecycleEpoch,
shouldHideConsoleWindow,
} from './process-policy';
/**
@@ -709,7 +708,7 @@ export class GatewayManager extends EventEmitter {
const { stdout } = await new Promise<{ stdout: string }>((resolve, reject) => {
import('child_process').then(cp => {
cp.exec(cmd, { timeout: 5000, windowsHide: shouldHideConsoleWindow() }, (err, stdout) => {
cp.exec(cmd, { timeout: 5000, windowsHide: true }, (err, stdout) => {
if (err) resolve({ stdout: '' });
else resolve({ stdout });
});
@@ -739,7 +738,7 @@ export class GatewayManager extends EventEmitter {
import('child_process').then(cp => {
cp.exec(
`taskkill /PID ${pid} /T /F`,
{ timeout: 5000, windowsHide: shouldHideConsoleWindow() },
{ timeout: 5000, windowsHide: true },
() => { }
);
}).catch(() => { });
@@ -844,7 +843,7 @@ export class GatewayManager extends EventEmitter {
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
shell: false,
windowsHide: shouldHideConsoleWindow(),
windowsHide: true,
env: spawnEnv,
});
@@ -1098,7 +1097,7 @@ export class GatewayManager extends EventEmitter {
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
shell: useShell,
windowsHide: shouldHideConsoleWindow(),
windowsHide: true,
env: spawnEnv,
});
const child = this.process;

View File

@@ -1,7 +1,3 @@
export function shouldHideConsoleWindow(platform: NodeJS.Platform = process.platform): boolean {
return platform === 'win32';
}
export function nextLifecycleEpoch(currentEpoch: number): number {
return currentEpoch + 1;
}

View File

@@ -526,6 +526,7 @@ export async function validateChannelConfig(channelType: string): Promise<Valida
cwd: openclawPath,
encoding: 'utf-8',
timeout: 30000,
windowsHide: true,
},
(err, stdout) => {
if (err) reject(err);

View File

@@ -269,6 +269,7 @@ export function generateCompletionCache(): void {
},
stdio: 'ignore',
detached: false,
windowsHide: true,
});
child.on('close', (code) => {
@@ -305,7 +306,8 @@ export function installCompletionToProfile(): void {
},
stdio: 'ignore',
detached: false,
},
windowsHide: true,
}
);
child.on('close', (code) => {

View File

@@ -14,7 +14,7 @@ function getBundledUvPath(): string {
const arch = process.arch;
const target = `${platform}-${arch}`;
const binName = platform === 'win32' ? 'uv.exe' : 'uv';
if (app.isPackaged) {
return join(process.resourcesPath, 'bin', binName);
} else {
@@ -53,7 +53,7 @@ function resolveUvBin(): { bin: string; source: 'bundled' | 'path' | 'bundled-fa
function findUvInPathSync(): boolean {
try {
const cmd = process.platform === 'win32' ? 'where.exe uv' : 'which uv';
execSync(cmd, { stdio: 'ignore', timeout: 5000 });
execSync(cmd, { stdio: 'ignore', timeout: 5000, windowsHide: true });
return true;
} catch {
return false;
@@ -95,6 +95,7 @@ export async function isPythonReady(): Promise<boolean> {
try {
const child = spawn(useShell ? quoteForCmd(uvBin) : uvBin, ['python', 'find', '3.12'], {
shell: useShell,
windowsHide: true,
});
child.on('close', (code) => resolve(code === 0));
child.on('error', () => resolve(false));
@@ -121,6 +122,7 @@ async function runPythonInstall(
const child = spawn(useShell ? quoteForCmd(uvBin) : uvBin, ['python', 'install', '3.12'], {
shell: useShell,
env,
windowsHide: true,
});
child.stdout?.on('data', (data) => {
@@ -210,12 +212,13 @@ export async function setupManagedPython(): Promise<void> {
const child = spawn(verifyShell ? quoteForCmd(uvBin) : uvBin, ['python', 'find', '3.12'], {
shell: verifyShell,
env: { ...process.env, ...uvEnv },
windowsHide: true,
});
let output = '';
child.stdout?.on('data', (data) => { output += data; });
child.on('close', () => resolve(output.trim()));
});
if (findPath) {
logger.info(`Managed Python 3.12 installed at: ${findPath}`);
}

View File

@@ -3,21 +3,9 @@ import {
getReconnectSkipReason,
isLifecycleSuperseded,
nextLifecycleEpoch,
shouldHideConsoleWindow,
} from '@electron/gateway/process-policy';
describe('gateway process policy helpers', () => {
describe('shouldHideConsoleWindow', () => {
it('returns true on Windows', () => {
expect(shouldHideConsoleWindow('win32')).toBe(true);
});
it('returns false on non-Windows platforms', () => {
expect(shouldHideConsoleWindow('darwin')).toBe(false);
expect(shouldHideConsoleWindow('linux')).toBe(false);
});
});
describe('lifecycle epoch helpers', () => {
it('increments lifecycle epoch by one', () => {
expect(nextLifecycleEpoch(0)).toBe(1);