Skip to content

Touch runtime.json updated_at on real routine fires - #688

Open
akeslo wants to merge 1 commit into
gtapps:mainfrom
akeslo:fix/routine-fire-touches-runtime-updated-at
Open

Touch runtime.json updated_at on real routine fires#688
akeslo wants to merge 1 commit into
gtapps:mainfrom
akeslo:fix/routine-fire-touches-runtime-updated-at

Conversation

@akeslo

@akeslo akeslo commented Aug 6, 2026

Copy link
Copy Markdown

Problem

An always-on session that's cycling background routines via the Monitor mechanism (routine-monitor.sh polling due.ts) never touches runtime.json's updated_at unless an explicit session open/close/heartbeat transition happens. Routine fires only append to state/routine-metrics.jsonl via logRoutineEvent.

evaluate-session.ts's stale-session nudge (and any downstream stale-session detector) reads that gap as "no recent activity," even though the session is doing exactly what it's designed to do: standing by between scheduled routine cycles without a fresh Progress Log line.

Fix

logRoutineEvent in scripts/lib/routines/event.ts now bumps runtime.json's updated_at whenever a routine actually fires or starts (event === 'fired' || event === 'started'). Skipped events (skipped-paused, skipped-waiting) are excluded since they aren't real activity. The write is fail-soft — a missing or corrupt runtime.json doesn't block the routine event itself.

Testing

  • bunx tsc --noEmit clean from repo root.
  • bun test tests/evaluate-session-stale.test.ts — 7/7 pass, no changes needed to existing regression coverage.
  • bun test tests/ — 3398 pass, 8 pre-existing failures all unrelated (macOS /tmp vs /private/tmp realpath mismatches in cc-compat-hermitdir.test.ts / hermit-run.test.ts), confirmed untouched by this change.

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.
@gtapps
gtapps requested a lite review from Copilot August 6, 2026 18:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates routine event logging to also “touch” the session runtime state when real routine activity occurs, preventing active sessions from being considered stale.

Changes:

  • Add localISOStamp import and use it to update runtime.json’s updated_at.
  • After successfully appending a routine event, update runtime.json for fired / started events in a fail-soft manner.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return appendJsonlLine(
const err = appendJsonlLine(
metrics,
JSON.stringify({ ts: utcISOStamp(), routine_id: id, event, delivery }),
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();
Comment on lines +81 to +86
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);
Comment on lines +84 to +87
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 */ }
@gtapps

gtapps commented Aug 8, 2026

Copy link
Copy Markdown
Owner

@akeslo did you encounter this issue on the latest 1.2.36 version? this should already be fixed with these commtis:

  • 817a692 determinize lifecycle bookkeeping into scripts
  • a34a435 stop lifecycle bookkeeping from destroying a queued close
  • c6452a0 guard against duplicate lifecycle instances and orphaned stops

Before those, sessions could get stuck in_progress between routine cycles

To update to the latest version run the skill: /claude-code-hermit:evolve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants