fix windows
This commit is contained in:
parent
228e8868ed
commit
697f5bb896
3 changed files with 50 additions and 10 deletions
|
|
@ -17,7 +17,7 @@
|
|||
* - Human-readable error messages per phase
|
||||
*/
|
||||
|
||||
import { execCli, spawnCli } from '@main/utils/childProcess';
|
||||
import { execCli, killProcessTree, spawnCli } from '@main/utils/childProcess';
|
||||
import { getHomeDir } from '@main/utils/pathDecoder';
|
||||
import { getErrorMessage } from '@shared/utils/errorHandling';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
|
|
@ -467,7 +467,7 @@ export class CliInstallerService {
|
|||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
child.kill();
|
||||
killProcessTree(child);
|
||||
reject(
|
||||
new Error(
|
||||
`Timed out after ${INSTALL_TIMEOUT_MS / 1000}s. ` +
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable no-param-reassign -- ProvisioningRun object is intentionally mutated as a state tracker throughout the provisioning lifecycle */
|
||||
import { ConfigManager } from '@main/services/infrastructure/ConfigManager';
|
||||
import { spawnCli } from '@main/utils/childProcess';
|
||||
import { killProcessTree, spawnCli } from '@main/utils/childProcess';
|
||||
import {
|
||||
encodePath,
|
||||
extractBaseDir,
|
||||
|
|
@ -1035,7 +1035,7 @@ export class TeamProvisioningService {
|
|||
void (async () => {
|
||||
const readyOnTimeout = await this.tryCompleteAfterTimeout(run);
|
||||
run.child?.stdin?.end();
|
||||
run.child?.kill();
|
||||
killProcessTree(run.child);
|
||||
if (readyOnTimeout) {
|
||||
return; // cleanupRun already called inside tryCompleteAfterTimeout
|
||||
}
|
||||
|
|
@ -1344,7 +1344,7 @@ export class TeamProvisioningService {
|
|||
void (async () => {
|
||||
const readyOnTimeout = await this.tryCompleteAfterTimeout(run);
|
||||
run.child?.stdin?.end();
|
||||
run.child?.kill();
|
||||
killProcessTree(run.child);
|
||||
if (readyOnTimeout) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1395,7 +1395,7 @@ export class TeamProvisioningService {
|
|||
run.cancelRequested = true;
|
||||
run.processKilled = true;
|
||||
run.child?.stdin?.end();
|
||||
run.child?.kill();
|
||||
killProcessTree(run.child);
|
||||
const progress = updateProgress(run, 'cancelled', 'Provisioning cancelled by user');
|
||||
run.onProgress(progress);
|
||||
this.cleanupRun(run);
|
||||
|
|
@ -1824,7 +1824,7 @@ export class TeamProvisioningService {
|
|||
run.processKilled = true;
|
||||
run.cancelRequested = true;
|
||||
run.child?.stdin?.end();
|
||||
run.child?.kill();
|
||||
killProcessTree(run.child);
|
||||
const progress = updateProgress(run, 'disconnected', 'Team stopped by user');
|
||||
run.onProgress(progress);
|
||||
this.cleanupRun(run);
|
||||
|
|
@ -1966,7 +1966,7 @@ export class TeamProvisioningService {
|
|||
// Kill the process on provisioning error
|
||||
run.processKilled = true;
|
||||
run.child?.stdin?.end();
|
||||
run.child?.kill();
|
||||
killProcessTree(run.child);
|
||||
this.cleanupRun(run);
|
||||
} else if (run.provisioningComplete) {
|
||||
// Post-provisioning error: process alive, waiting for input
|
||||
|
|
@ -2028,7 +2028,7 @@ export class TeamProvisioningService {
|
|||
run.onProgress(progress);
|
||||
run.processKilled = true;
|
||||
run.child?.stdin?.end();
|
||||
run.child?.kill();
|
||||
killProcessTree(run.child);
|
||||
this.cleanupRun(run);
|
||||
return;
|
||||
}
|
||||
|
|
@ -3137,7 +3137,7 @@ export class TeamProvisioningService {
|
|||
const stderrChunks: Buffer[] = [];
|
||||
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
child.kill();
|
||||
killProcessTree(child);
|
||||
reject(new Error(`Timeout running: claude ${args.join(' ')}`));
|
||||
}, timeoutMs);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
type ChildProcess,
|
||||
exec,
|
||||
execFile,
|
||||
type ExecFileOptions,
|
||||
|
|
@ -6,6 +7,7 @@ import {
|
|||
spawn,
|
||||
type SpawnOptions,
|
||||
} from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Promise wrapper for execFile that always returns { stdout, stderr }.
|
||||
|
|
@ -156,3 +158,41 @@ export function spawnCli(
|
|||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill a child process and its entire process tree.
|
||||
*
|
||||
* On Windows with `shell: true`, `child.kill()` only kills the intermediate
|
||||
* `cmd.exe` shell, leaving the actual process (e.g. `claude.cmd`) orphaned.
|
||||
* `taskkill /T /F /PID` recursively kills the entire process tree.
|
||||
*
|
||||
* On macOS/Linux, processes are killed directly (no shell wrapper), so
|
||||
* the standard `child.kill(signal)` works correctly.
|
||||
*/
|
||||
export function killProcessTree(
|
||||
child: ChildProcess | null | undefined,
|
||||
signal?: NodeJS.Signals
|
||||
): void {
|
||||
if (!child?.pid) {
|
||||
// Process is null, never started, or already exited
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
const taskkillPath = path.join(
|
||||
process.env.SystemRoot ?? 'C:\\Windows',
|
||||
'System32',
|
||||
'taskkill.exe'
|
||||
);
|
||||
execFile(taskkillPath, ['/T', '/F', '/PID', String(child.pid)], () => {
|
||||
// Best-effort — ignore errors (process may have already exited)
|
||||
});
|
||||
return;
|
||||
} catch {
|
||||
// taskkill failed, fall through to standard kill
|
||||
}
|
||||
}
|
||||
|
||||
child.kill(signal);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue