From 14c77ff8c52bda0efe4ee65d1766c5ed2dc636ca Mon Sep 17 00:00:00 2001 From: akeslo <3003773+akeslo@users.noreply.github.com> Date: Thu, 6 Aug 2026 03:51:13 -0400 Subject: [PATCH] Touch runtime.json updated_at on real routine fires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An always-on session cycling background routines via the Monitor mechanism never updates runtime.json's updated_at unless a session open/close/heartbeat transition happens — routine fires only append to routine-metrics.jsonl. evaluate-session.ts's stale-session nudge (and the dashboard's stale-session detector downstream) reads that staleness as "session may be hung," even when the session is doing exactly what it's supposed to: standing by between scheduled routine cycles. Have logRoutineEvent bump runtime.json's updated_at whenever a routine actually fires or starts (not on skipped-paused/skipped-waiting, which aren't real activity). Fail-soft: a missing/corrupt runtime.json must not block the routine event itself. --- .../scripts/lib/routines/event.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/plugins/claude-code-hermit/scripts/lib/routines/event.ts b/plugins/claude-code-hermit/scripts/lib/routines/event.ts index 1e634edf..1309afbd 100644 --- a/plugins/claude-code-hermit/scripts/lib/routines/event.ts +++ b/plugins/claude-code-hermit/scripts/lib/routines/event.ts @@ -9,7 +9,7 @@ import fs from 'node:fs'; import path from 'node:path'; -import { utcISOStamp } from '../time'; +import { utcISOStamp, localISOStamp } from '../time'; import { appendJsonlLine } from '../append-jsonl'; // Deliberately not an enum check: the shell version accepted any event string, @@ -62,10 +62,32 @@ export function logRoutineEvent( } catch { /* no ledger yet — nothing to dedup against */ } } - return appendJsonlLine( + const err = appendJsonlLine( metrics, JSON.stringify({ ts: utcISOStamp(), routine_id: id, event, delivery }), ); + if (err) return err; + + // A routine that actually ran (as opposed to skipped-paused/skipped-waiting) is + // real session activity. Without this, an always-on session cycling routines + // via Monitor between operator turns never touches runtime.json's `updated_at` + // — only session-archive.ts's open/close/heartbeat transitions do — so + // evaluate-session.ts's stale-session nudge fires on a session that is working + // exactly as designed, just not writing Progress Log lines for background + // routine cycles. Touching updated_at here is fail-soft: a failure must not + // block or fail the routine event itself. + if (event === 'fired' || event === 'started') { + try { + const runtimePath = path.join(root, '.claude-code-hermit', 'state', 'runtime.json'); + const runtime = JSON.parse(fs.readFileSync(runtimePath, 'utf-8')); + runtime.updated_at = localISOStamp(); + const tmp = `${runtimePath}.${process.pid}.tmp`; + fs.writeFileSync(tmp, JSON.stringify(runtime, null, 2) + '\n', 'utf-8'); + fs.renameSync(tmp, runtimePath); + } catch { /* fail-soft — runtime.json missing/corrupt must not block the routine */ } + } + + return null; } export function run(args: string[]): void {