Re-key recovery-stalled alarm to the family sum of all reason series - #3934
Conversation
…il on main) Adds scripts/lib/test_eval_alarms_recovery_family.py and a catalog TAP assertion (Test 41c) in test-monitor-skill-snippets.sh. On origin/main these fail: eval_counter_streak has no `prev` param, post_restart_absolute_label is unhandled (family sum false-fires the absolute guard), validate_catalog does not reject a non-string label, render_aggregate has no reason breakdown, and the catalog stanza is still extraction=form2 + single-label selector. Refs #3824 Co-authored-by: Claude Code <claude-code@anthropic.com>
Check 12b (recovery-stalled counter-streak) keyed only on
henyey_recovery_stalled_tick_total{reason="forcing_catchup_behind"}. During an
at-tip stall the node takes the forcing_catchup_not_behind branch by
construction, so the one label the alarm watched was exactly the branch that
could not move — a real recovery episode incremented two uncovered labels and
the tick reported ok (delta=0).
Re-key the delta/streak/burst trigger onto the SUM of every reason series
(extraction = form2-sum-all). Scope the post-restart absolute guard to a single
calibrated label via a new post_restart_absolute_label field, so the summed
warmup value (~113) does not false-fire the #3197/#3198 absolute threshold
tuned for forcing_catchup_behind alone. Attach a per-reason breakdown on
breach/firing so a summed fire names the moving labels, on the status line and
in filing details. Rename the snapshot file to force a clean re-baseline on
deploy (empty new path → collecting_baseline, no spurious post-restart fire),
bump baseline_version 3→4, and generalize the cooldown/filing identity to the
family.
Streak-3 / burst-10 gating still absorbs transient single-tick blips (including
the deliberately-non-alarming near_tip_gap1_suppressed / near_tip_park_inflated
suppression counters the node now also exports), so #3728's false-positive class
does not return.
Refs #3824
Co-authored-by: Claude Code <claude-code@anthropic.com>
🔍 Reviewer: CorrectnessVerdict: APPROVE Summary: Re-keys the recovery-stalled counter-streak from a single Full reviewBug-fix test gate (kind:bug): PASS.
New public surface — all covered:
Logic consistency: the streak baseline snapshots the family sum ( Non-blocking (advisory, already documented in code comments): the per-reason breakdown deltas are computed against No new-class concerns. APPROVE. |
🔍 Reviewer: RiskVerdict: APPROVE Summary: Monitoring-only change (Check 12b alarm re-key + eval-alarms.py machinery). No observable/interop surface is touched, so this is out of parity scope. Baseline migration, versioning, and dual-catalog identity are all handled correctly. Cycle 1 — complete review. Full reviewParity scope: None. Changed files are Migration / operational risk — handled:
Catalog integrity: Behavioral risk — summing suppression counters (#3728 regression class): the family sum now includes the deliberately-non-alarming No security or data-format exposure. APPROVE. |
✅ MergedCommit: 854e23b Follow-up issues filed for unaddressed inline review comments: none (no unresolved inline threads) |
Closes #3824
Summary
Check 12b (the
recovery-stalledcounter-streak) watched a singlereasonseries —
henyey_recovery_stalled_tick_total{reason="forcing_catchup_behind"}—of the node's recovery-reason family. During an at-tip stall the node takes
the
forcing_catchup_not_behindbranch by construction, so the one label thealarm watched was exactly the branch that could not move: a real recovery
episode incremented two uncovered labels (
forcing_catchup_not_behind,near_tip_peer_scp_recovery) and the tick reportedok (delta=0).This re-keys the delta/streak/burst trigger onto the sum of every
reasonseries (
extraction = "form2-sum-all"), so the trigger observes whicheverbranch actually moves and automatically covers any future
reason. Thepost-restart absolute guard (#3197/#3198) is scoped to a single calibrated label
via a new
post_restart_absolute_label = "forcing_catchup_behind"field — thesummed family legitimately reaches ~113 during warmup and would otherwise
false-fire the absolute threshold on every restart. A per-reason breakdown is
attached on breach/firing (
[by reason: <label>+<delta>, …]) so a summed firestill names the moving labels, on the status line and in filing details.
Baseline migration uses the #3222 lever — the snapshot file is renamed
(
counter_streak_snapshot→recovery_family_streak_snapshot) so the oldbaseline is absent at the new path and the first post-migration tick re-collects
cleanly (no spurious post-restart fire).
baseline_versionbumped 3→4; cooldownand filing identity generalized to the family.
Plan reference
Converged Plan comment
Test plan
python3 -m pytest scripts/lib/ scripts/ci/— 161 passed (incl. newtest_eval_alarms_recovery_family.py)bash scripts/test-monitor-skill-snippets.sh— all TAP cases passscripts/ci/check-alarm-versions.pyold→new — semantic changes covered by thebaseline_versionbump--validate-onlypasses;.claude/.agentscatalog byte-identicalcargo fmt/clippyN/ARegression test (bug-fix)
scripts/lib/test_eval_alarms_recovery_family.py+ catalog TAP assertion (Test 41c) inscripts/test-monitor-skill-snippets.sh63304f7— verified FAILED onorigin/main(eval_counter_streakhas noprevparam;post_restart_absolute_labelunhandled so the family sum false-fires the absolute guard;validate_catalogdoes not reject a non-string label; no reason breakdown; catalog stillextraction=form2+ single-label selector)b43a243Deviations from plan
reasonseries". The node (crates/app/src/metrics.rs) now exports 10 reasons, two of which (near_tip_gap1_suppressed,near_tip_park_inflated) are deliberately non-alarming suppression counters.form2-sum-allsums them too. This is safe and intended: streak-3 / burst-10 gating absorbs the transient single-tick blips those counters represent, while a node genuinely parked at gap==1 (steadily climbing suppressed counter, flat ledger) is exactly the "worth manual investigation" condition the prior docs flagged — now surfaced on therecovery_stalled:line. metrics: henyey_recovery_stalled_tick_total{reason="forcing_catchup_behind"} — sustained breach #3728's false-positive class does not return. Documented in the Check 12b metrics: henyey_recovery_stalled_tick_total{reason="forcing_catchup_behind"} — sustained breach #3728 note..agents/Codex mirrors (snapshot filename, family-union table row/description, scoped post-restart guard, cooldown/filing identity), since both runtimes share the one catalog whosesnapshot_filechanged — leaving the mirror stale would make the Codex runtime write the wrong snapshot path./metrics/recovery_family_streak_snapshotto.gitignore(and its stray-name test list) alongside the retained old entry.henyey_recovery_tx_set_stuck_secondsalarm; a per-label independent-threshold model (superseded by the union approach).🤖 Generated with Claude Code