From 4e31acbd476ef5b50a68f1d7b7cc9c844c4710c8 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 14 Mar 2026 12:55:40 -0500 Subject: [PATCH] fix: auto-clear stale heartbeat when process is dead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- scripts/eval-watch.ts | 21 ++++++++++++++++++++- test/helpers/session-runner.ts | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/scripts/eval-watch.ts b/scripts/eval-watch.ts index 117d2bdb22cc69502316596489eeee278ce9033d..899ec9062898c3c8dc452ce5ebacf6d809ee397f 100644 --- a/scripts/eval-watch.ts +++ b/scripts/eval-watch.ts @@ -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(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(HEARTBEAT_PATH); + let heartbeat = readJSON(HEARTBEAT_PATH); const partial = readJSON(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'); diff --git a/test/helpers/session-runner.ts b/test/helpers/session-runner.ts index 17ed772c0ddbf9a38033c313efe5b05f583eb51b..6654df5f7048a76f1f2c2ca8b64e6a6ce8635a16 100644 --- a/test/helpers/session-runner.ts +++ b/test/helpers/session-runner.ts @@ -216,6 +216,7 @@ export async function runSkillTest(options: { const toolDesc = `${item.name}(${truncate(JSON.stringify(item.input || {}), 60)})`; atomicWriteSync(HEARTBEAT_PATH, JSON.stringify({ runId, + pid: proc.pid, startedAt, currentTest: testName, status: 'running',