feat(cockpit): loop-health panel — last tick, cadence, verdict history, stall detection (#85)#104
Merged
Merged
Conversation
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
approved these changes
Jul 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 withnode(safe escaping) and rotated via temp-file + atomicmv, mirroringlog-event.sh. Override path withCLAUDE_TICKS_FILE, cap withLOOP_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 toCOCKPIT_VERDICT_HISTORY_N, default 10), and a prominent STALLED banner whennow − lastTick > 2× cadence interval(120/600/1800s). Missing/empty log renders "loop not armed", never crashes. Readsloop-ticks.jsonlthe same way it readsevents.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_Ncap, missing-log → "loop not armed", and a deterministic STALLED case via injectedCOCKPIT_NOW.Gates
GATES_FILE=.claude/self/gates.jsonbuild / 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