fix: Windows console popup issue using PowerShell workaround
Fixes blank console windows appearing on Windows 11 when spawning the worker process. ## Problem On Windows, `windowsHide: true` doesn't work when combined with `detached: true` in child_process.spawn(). This is a Node.js limitation (nodejs/node#21825) that **Bun also inherits** because Bun uses Node.js process spawning semantics. Result: Blank console windows with "claude" title appear during claude-mem operations. ## Solution Use PowerShell's `Start-Process -WindowStyle Hidden` on Windows to properly hide console windows. Unix platforms continue using standard `spawn()` with `detached: true`. ## Testing Validated by ToxMox on Windows 11 in PR #315: - windowsHide approach: ❌ Still shows blank consoles - PowerShell approach: ✅ Properly hides windows ## Implementation ```typescript // Windows: PowerShell workaround Start-Process -FilePath 'bun' -ArgumentList '${script}' -WindowStyle Hidden // Unix: Standard spawn (works fine) spawn(bunPath, [script], { detached: true }) ``` ## Notes - Affects BOTH Bun and Node.js runtimes on Windows - This is a **high-priority fix** for Windows users - Keeps Bun runtime (doesn't address zombie socket issue) - Zombie socket issue is separate and requires different solution ## References - Issue: #304 (Multiple visible console windows on Windows 11) - Testing: PR #315 (ToxMox's detailed analysis) - Node.js bug: nodejs/node#21825 - Extracted from: PR #335 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
|
||||
import { createWriteStream } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
import { homedir } from 'os';
|
||||
import { DATA_DIR } from '../../shared/paths.js';
|
||||
import { getBunPath, isBunAvailable } from '../../utils/bun-path.js';
|
||||
@@ -52,7 +52,7 @@ export class ProcessManager {
|
||||
|
||||
const logFile = this.getLogFilePath();
|
||||
|
||||
// Use Bun on all platforms
|
||||
// Use Bun on all platforms with PowerShell workaround for Windows console popups
|
||||
return this.startWithBun(workerScript, logFile, port);
|
||||
}
|
||||
|
||||
@@ -68,40 +68,76 @@ export class ProcessManager {
|
||||
error: 'Bun is required but not found in PATH or common installation paths. Install from https://bun.sh'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
const child = spawn(bunPath, [script], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: { ...process.env, CLAUDE_MEM_WORKER_PORT: String(port) },
|
||||
cwd: MARKETPLACE_ROOT,
|
||||
// Hide console window on Windows
|
||||
...(isWindows && { windowsHide: true })
|
||||
});
|
||||
if (isWindows) {
|
||||
// Windows: Use PowerShell Start-Process with -WindowStyle Hidden
|
||||
// This properly hides the console window (affects both Bun and Node.js)
|
||||
// Note: windowsHide: true doesn't work with detached: true (Bun inherits Node.js process spawning semantics)
|
||||
// See: https://github.com/nodejs/node/issues/21825 and PR #315 for detailed testing
|
||||
const envVars = `$env:CLAUDE_MEM_WORKER_PORT='${port}'`;
|
||||
const psCommand = `${envVars}; Start-Process -FilePath '${bunPath}' -ArgumentList '${script}' -WorkingDirectory '${MARKETPLACE_ROOT}' -WindowStyle Hidden -PassThru | Select-Object -ExpandProperty Id`;
|
||||
|
||||
// Write logs
|
||||
const logStream = createWriteStream(logFile, { flags: 'a' });
|
||||
child.stdout?.pipe(logStream);
|
||||
child.stderr?.pipe(logStream);
|
||||
const result = spawnSync('powershell', ['-Command', psCommand], {
|
||||
stdio: 'pipe',
|
||||
timeout: 10000,
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
child.unref();
|
||||
if (result.status !== 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `PowerShell spawn failed: ${result.stderr?.toString() || 'unknown error'}`
|
||||
};
|
||||
}
|
||||
|
||||
if (!child.pid) {
|
||||
return { success: false, error: 'Failed to get PID from spawned process' };
|
||||
const pid = parseInt(result.stdout.toString().trim(), 10);
|
||||
if (isNaN(pid)) {
|
||||
return { success: false, error: 'Failed to get PID from PowerShell' };
|
||||
}
|
||||
|
||||
// Write PID file
|
||||
this.writePidFile({
|
||||
pid,
|
||||
port,
|
||||
startedAt: new Date().toISOString(),
|
||||
version: process.env.npm_package_version || 'unknown'
|
||||
});
|
||||
|
||||
// Wait for health
|
||||
return this.waitForHealth(pid, port);
|
||||
} else {
|
||||
// Unix: Use standard spawn with detached
|
||||
const child = spawn(bunPath, [script], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: { ...process.env, CLAUDE_MEM_WORKER_PORT: String(port) },
|
||||
cwd: MARKETPLACE_ROOT
|
||||
});
|
||||
|
||||
// Write logs
|
||||
const logStream = createWriteStream(logFile, { flags: 'a' });
|
||||
child.stdout?.pipe(logStream);
|
||||
child.stderr?.pipe(logStream);
|
||||
|
||||
child.unref();
|
||||
|
||||
if (!child.pid) {
|
||||
return { success: false, error: 'Failed to get PID from spawned process' };
|
||||
}
|
||||
|
||||
// Write PID file
|
||||
this.writePidFile({
|
||||
pid: child.pid,
|
||||
port,
|
||||
startedAt: new Date().toISOString(),
|
||||
version: process.env.npm_package_version || 'unknown'
|
||||
});
|
||||
|
||||
// Wait for health
|
||||
return this.waitForHealth(child.pid, port);
|
||||
}
|
||||
|
||||
// Write PID file
|
||||
this.writePidFile({
|
||||
pid: child.pid,
|
||||
port,
|
||||
startedAt: new Date().toISOString(),
|
||||
version: process.env.npm_package_version || 'unknown'
|
||||
});
|
||||
|
||||
// Wait for health
|
||||
return this.waitForHealth(child.pid, port);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
|
||||
Reference in New Issue
Block a user