Skip to content

feat(cockpit): loop-health panel — last tick, cadence, verdict history, stall detection (#85)#104

Merged
robercano-ghbot merged 3 commits into
mainfrom
feat/issue-85-loop-health-panel
Jul 9, 2026
Merged

feat(cockpit): loop-health panel — last tick, cadence, verdict history, stall detection (#85)#104
robercano-ghbot merged 3 commits into
mainfrom
feat/issue-85-loop-health-panel

Conversation

@robercano-ghbot

Copy link
Copy Markdown
Collaborator

Closes #85.

What

Adds a Loop health panel to the cockpit so the autonomous loop's own liveness is observable (previously the cockpit showed workers and PRs but nothing about the thing driving them — a silent 13-hour stall went unnoticed once).

Changes

  • loop-tick.sh — appends one tick record per firing to .claude/state/loop-ticks.jsonl (ts + raw verdict + cadence + parsed action/issue/pr), built with node (safe escaping) and rotated via temp-file + atomic mv, mirroring log-event.sh. Override path with CLAUDE_TICKS_FILE, cap with LOOP_TICKS_MAX_LINES (default 2000). The write is silent-to-file and best-effort, so the machine-readable verdict stays the last stdout line and a failed write never aborts the tick.
  • cockpit.sh — new <section id="loop-health">: last tick, current cadence (FAST/WATCH/IDLE), verdict history (newest-first, capped to COCKPIT_VERDICT_HISTORY_N, default 10), and a prominent STALLED banner when now − lastTick > 2× cadence interval (120/600/1800s). Missing/empty log renders "loop not armed", never crashes. Reads loop-ticks.jsonl the same way it reads events.jsonl, so it appears in both static and serve/SSE mode with no new endpoint. Zero agent tokens — all computed harness-side.

Tests

  • loop-tick.test.sh (40 checks): record write/field correctness for advance/none/feedback verdicts, rotation cap with exact retained-order assertion + boundary case, and the verdict-still-last-line invariant (including the best-effort failure path).
  • cockpit.test.sh (64 checks): populated render, verdict-history ordering + COCKPIT_VERDICT_HISTORY_N cap, missing-log → "loop not armed", and a deterministic STALLED case via injected COCKPIT_NOW.

Gates

GATES_FILE=.claude/self/gates.json build / lint / test all pass (incl. smoke-fanout.sh).

Review

Passed both self-adapter lenses (correctness, tests) with consensus=all. The rotation test was hardened from a vacuous count-only check to an order-sensitive assertion (verified it catches a reversed-slice regression), and the verdict-history table was bounded to "last N" per the issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EXQ49ycXNDBzi8x15n2jsc

robercano and others added 3 commits July 9, 2026 22:16
Cover write_tick_record (issue #85): one JSONL line per firing with the
expected fields, action=none/advance/feedback parsing, rotation via
LOOP_TICKS_MAX_LINES, and the invariant that the verdict stays the LAST
stdout line even when the tick log can't be written at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Render a new "Loop health" section in cockpit.sh sourced from loop-tick.sh's
tick-record log (loop-ticks.jsonl): last tick + verdict, current cadence,
full verdict history newest-first, and a STALLED banner once a tick is
overdue by more than 2x its cadence's expected interval (FAST 60s->120s,
WATCH 300s->600s, IDLE 900s->1800s). Mirrors the events.jsonl offline seam
(--fixtures mode reads <dir>/loop-ticks.jsonl) and degrades to "loop not
armed" on a missing/empty log instead of crashing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rdict-history cap)

- loop-tick.test.sh scenario 13 was vacuous: it replayed the SAME static
  fixture for every rotation iteration, so retained lines were byte-identical
  and only line-count/JSON-validity were checked. Each iteration now gets a
  distinguishable advance_ready/issue, and the test asserts the EXACT retained
  issue values in order (mirroring log-event.test.sh's `want` pattern), plus a
  boundary case for writing exactly LOOP_TICKS_MAX_LINES ticks. Verified this
  now fails when the rotation slice direction is reversed locally.

- cockpit.sh's renderLoopHealth() rendered ALL retained ticks (up to the
  2000-line rotation cap) instead of "the last N verdict lines, newest first"
  per issue #85. Added COCKPIT_VERDICT_HISTORY_N (default 10, consistent with
  the existing COCKPIT_NOW/CLAUDE_TICKS_FILE override style) to cap the
  verdict-history table; "last tick"/"cadence" still come from the single
  most recent tick. Added cockpit.test.sh cases for the cap (exact N rows,
  newest first) and confirming the default doesn't truncate a shorter history.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXQ49ycXNDBzi8x15n2jsc
@robercano-ghbot
robercano-ghbot merged commit 028261a into main Jul 9, 2026
9 checks passed
@robercano-ghbot
robercano-ghbot deleted the feat/issue-85-loop-health-panel branch July 9, 2026 21:20
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.

Cockpit: loop-health panel — last tick, cadence, verdict history, stall detection

2 participants