fix: harden live-server liveness against pid reuse

greptile-apps[bot] repro: a helper that died without removing
server.json leaves a pid the OS can hand to an unrelated process, which
kill(pid, 0) classifies as a running server and routes repo-root helpers
onto the stale app. The liveness check now also requires the pid's
command line to look like a node process (ps-based, platform-guarded),
removing reuse by arbitrary processes; the residual node-reuse case is
covered by the multi-app warning and the --target escape hatch.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-27 17:07:25 -07:00
co-authored by Claude Code
parent 5a85050230
commit 031e170d3e
2 changed files with 50 additions and 4 deletions
+21 -4
View File
@@ -27,6 +27,7 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { resolveProjectRoot } from '../context.mjs';
const ROOTS_MANIFEST_VERSION = 1;
@@ -284,16 +285,32 @@ function readPointerEntries(repoRoot) {
}
}
/** True when the app's live helper server is recorded and its pid is alive. */
/**
* True when the app's live helper server is recorded and its pid is alive.
* A liveness signal alone misclassifies a REUSED pid (helper died without
* removing server.json, the OS handed the pid to something else), so the
* process's command line must also look like a node process; that removes
* reuse by arbitrary processes. A pid reused by another node process remains
* a residual false positive, which the multi-app warning and --target
* escape hatch cover.
*/
function hasLiveServer(appRoot) {
let pid;
try {
const info = JSON.parse(fs.readFileSync(path.join(appRoot, '.impeccable', 'live', 'server.json'), 'utf-8'));
if (!info || typeof info.pid !== 'number') return false;
process.kill(info.pid, 0);
return true;
pid = info.pid;
process.kill(pid, 0);
} catch (err) {
// EPERM: the process exists but is not signalable by this user.
return err?.code === 'EPERM';
if (err?.code !== 'EPERM') return false;
}
if (process.platform === 'win32') return true; // no cheap portable command check
try {
const command = execFileSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf-8' });
return /\b(node|bun|live-server)\b/.test(command);
} catch {
return false;
}
}