From 9a3f5aa34b7e1e6cb8b916e3cfee1564ab32e632 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Tue, 28 Jul 2026 14:33:11 -0700 Subject: [PATCH] fix: portable port probe for live-server liveness greptile-apps[bot]: the win32 branch skipped the port probe entirely (bash /dev/tcp is not portable), so a reused pid on Windows still classified as a running helper. The probe is now a spawned node one-liner that behaves identically on every platform, which also drops the bash dependency for minimal Linux environments; the ps identity check remains only for legacy server.json records without a port. This work was produced with AI assistance (Claude Code). Co-Authored-By: Claude Code --- skill/scripts/live/roots.mjs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/skill/scripts/live/roots.mjs b/skill/scripts/live/roots.mjs index ac8750068..1adab11d4 100644 --- a/skill/scripts/live/roots.mjs +++ b/skill/scripts/live/roots.mjs @@ -313,16 +313,23 @@ function hasLiveServer(appRoot) { // Liveness alone misclassifies a REUSED pid (helper died without removing // server.json, the OS handed the pid to something else, even another node // process). The decisive signal is the recorded PORT: a real helper is - // listening on it, a pid squatter is not. - if (Number.isInteger(port) && port > 0 && process.platform !== 'win32') { + // listening on it, a pid squatter is not. The probe is a spawned node + // one-liner so it works identically on every platform (no bash, no ps). + if (Number.isInteger(port) && port > 0) { try { - execFileSync('bash', ['-c', `exec 3<>/dev/tcp/127.0.0.1/${port}`], { timeout: 1500, stdio: 'ignore' }); + execFileSync(process.execPath, ['-e', [ + "const s = require('node:net').connect({ host: '127.0.0.1', port: Number(process.argv[1]), timeout: 800 });", + "s.on('connect', () => { s.destroy(); process.exit(0); });", + "s.on('timeout', () => { s.destroy(); process.exit(1); });", + "s.on('error', () => process.exit(1));", + ].join(''), String(port)], { timeout: 3000, stdio: 'ignore' }); return true; } catch { return false; } } - if (process.platform === 'win32') return true; // no cheap portable probe + // Legacy server.json without a port: best-effort process identity check. + if (process.platform === 'win32') return true; try { const command = execFileSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf-8' }); return /live-server|\b(node|bun)\b/.test(command);