diff --git a/setup-claude-server.js b/setup-claude-server.js index b140ce9f..e0493c97 100755 --- a/setup-claude-server.js +++ b/setup-claude-server.js @@ -533,6 +533,19 @@ function updateSetupStep(index, status, error = null) { } } +// Resolve a system utility to an absolute path so it can't be shadowed by a +// foreign entry prepended to PATH (shell rc files, direnv, tool shims, etc.). +// Returns the first candidate that exists on disk, falling back to the bare +// name if none are found (e.g. a distro that installs the tool elsewhere). +function resolveSystemBinary(candidates, fallback) { + for (const candidate of candidates) { + if (existsSync(candidate)) { + return candidate; + } + } + return fallback; +} + async function execAsync(command) { const execStep = addSetupStep(`exec_${command.substring(0, 20)}...`); return new Promise((resolve, reject) => { @@ -569,16 +582,20 @@ async function restartClaude() { `taskkill /F /IM "Claude.exe"`, ); break; - case "darwin": + case "darwin": { + const killall = resolveSystemBinary(['/usr/bin/killall'], 'killall'); await execAsync( - `killall "Claude"`, + `"${killall}" "Claude"`, ); break; - case "linux": + } + case "linux": { + const pkill = resolveSystemBinary(['/usr/bin/pkill', '/bin/pkill'], 'pkill'); await execAsync( - `pkill -f "claude"`, + `"${pkill}" -f "claude"`, ); break; + } } updateSetupStep(killStep, 'completed'); await trackEvent('npx_setup_kill_claude_success', { platform }); @@ -600,7 +617,8 @@ async function restartClaude() { updateSetupStep(startStep, 'skipped'); await trackEvent('npx_setup_start_claude_skipped', { platform }); } else if (platform === "darwin") { - await execAsync(`open -a "Claude"`); + const open = resolveSystemBinary(['/usr/bin/open'], 'open'); + await execAsync(`"${open}" -a "Claude"`); updateSetupStep(startStep, 'completed'); logToFile("\nāœ… Claude has been restarted automatically!"); await trackEvent('npx_setup_start_claude_success', { platform }); diff --git a/src/remote-device/remote-channel.ts b/src/remote-device/remote-channel.ts index 0f42478d..c5a0ac59 100644 --- a/src/remote-device/remote-channel.ts +++ b/src/remote-device/remote-channel.ts @@ -542,7 +542,11 @@ export class RemoteChannel { console.debug('[DEBUG] Spawning blocking update script:', scriptPath); console.debug('[DEBUG] Using node executable:', process.execPath); - const result = spawnSync('node', [ + // process.execPath is the exact Node binary currently running the + // server. Using it (instead of the bare name 'node') skips PATH + // resolution entirely, so the update script always runs on the same + // runtime and can't be shadowed by a different 'node' on PATH. + const result = spawnSync(process.execPath, [ scriptPath, deviceId, supabaseUrl, diff --git a/uninstall-claude-server.js b/uninstall-claude-server.js index d279602d..78db0424 100644 --- a/uninstall-claude-server.js +++ b/uninstall-claude-server.js @@ -412,6 +412,15 @@ function updateUninstallStep(index, status, error = null) { } } +function resolveSystemBinary(candidates, fallback) { + for (const candidate of candidates) { + if (existsSync(candidate)) { + return candidate; + } + } + return fallback; +} + async function execAsync(command) { const execStep = addUninstallStep(`exec_${command.substring(0, 20)}...`); return new Promise((resolve, reject) => { @@ -489,12 +498,16 @@ async function restartClaude() { case "win32": await execAsync(`taskkill /F /IM "Claude.exe"`); break; - case "darwin": - await execAsync(`killall "Claude"`); + case "darwin": { + const killall = resolveSystemBinary(['/usr/bin/killall'], 'killall'); + await execAsync(`"${killall}" "Claude"`); break; - case "linux": - await execAsync(`pkill -f "claude"`); + } + case "linux": { + const pkill = resolveSystemBinary(['/usr/bin/pkill', '/bin/pkill'], 'pkill'); + await execAsync(`"${pkill}" -f "claude"`); break; + } } updateUninstallStep(killStep, 'completed'); logToFile("Claude process terminated successfully"); @@ -516,7 +529,8 @@ async function restartClaude() { updateUninstallStep(startStep, 'skipped'); await trackEvent('uninstall_start_claude_skipped'); } else if (platform === "darwin") { - await execAsync(`open -a "Claude"`); + const open = resolveSystemBinary(['/usr/bin/open'], 'open'); + await execAsync(`"${open}" -a "Claude"`); updateUninstallStep(startStep, 'completed'); logToFile("āœ… Claude has been restarted automatically!"); await trackEvent('uninstall_start_claude_success');