fix: auto-clear stale heartbeat when process is dead

Add PID to heartbeat file. eval-watch checks process.kill(pid, 0) and
auto-deletes the heartbeat when the PID is no longer alive — no manual
cleanup needed after crashed/killed E2E runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-14 12:55:40 -05:00
parent 43fbe165a4
commit 4e31acbd47
2 changed files with 21 additions and 1 deletions
+20 -1
View File
@@ -19,6 +19,7 @@ const STALE_THRESHOLD_SEC = 600; // 10 minutes
export interface HeartbeatData {
runId: string;
pid?: number;
startedAt: string;
currentTest: string;
status: string;
@@ -51,6 +52,16 @@ function readJSON<T>(filePath: string): T | null {
}
}
/** Check if a process is alive (signal 0 = existence check, doesn't kill). */
function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
/** Format seconds as Xm Ys */
function formatDuration(sec: number): string {
if (sec < 60) return `${sec}s`;
@@ -127,9 +138,17 @@ if (import.meta.main) {
const showTail = process.argv.includes('--tail');
const render = () => {
const heartbeat = readJSON<HeartbeatData>(HEARTBEAT_PATH);
let heartbeat = readJSON<HeartbeatData>(HEARTBEAT_PATH);
const partial = readJSON<PartialData>(PARTIAL_PATH);
// Auto-clear heartbeat if the process is dead
if (heartbeat?.pid && !isProcessAlive(heartbeat.pid)) {
try { fs.unlinkSync(HEARTBEAT_PATH); } catch { /* already gone */ }
process.stdout.write('\x1B[2J\x1B[H');
process.stdout.write(`Cleared stale heartbeat — PID ${heartbeat.pid} is no longer running.\n\n`);
heartbeat = null;
}
// Clear screen
process.stdout.write('\x1B[2J\x1B[H');
process.stdout.write(renderDashboard(heartbeat, partial) + '\n');