Give crontab watchdogs a real tick post-condition - #3915
Conversation
Adds scripts/test-watchdog-postcondition.sh: a TAP harness pinning the watchdog post-condition fix. Asserts watchdog_classify_outcome distinguishes a real tick (new tick-history.jsonl row above a duration floor) from a spend-limit refusal no-op, an empty no-op, and a suspect-fast write; and that both crontab watchdogs write the refire-cooldown MARKER only on a real success and log a distinct outcome=<class> line. Fails on main (shared lib and canonical scripts/monitor-watchdog.sh absent; project-loop-watchdog.sh logs bare exit=$rc with a pre-tick MARKER write). Refs #3780 Co-authored-by: Claude Code <claude-code@anthropic.com>
`claude -p`'s exit status is the CLI's, not the tick's: a refusal to run (org monthly spend limit, auth failure) exits 0 and was logged as a successful tick. On 2026-07-28/29 this produced ~24h of dark mainnet monitoring recorded as 49 "successful" ticks, and because the refire-cooldown MARKER was written before the launch, each 1-second no-op burned the full 30-min cooldown. Add scripts/lib/watchdog-postcondition.sh — a source-safe classifier keyed on the only true success signal, a NEW tick-history.jsonl row: success (row advanced, duration >= floor), suspect-fast (row advanced but under the floor), noop-refusal (no row, output matches a known refusal regex), noop-empty (no row, no recognized reason). Both watchdogs now capture the row count and duration around the launch, log a distinct outcome=<class> line, write the cooldown MARKER only on success (so a no-op retries at the next */15 slot), and emit a greppable ESCALATION line after N consecutive failed launches. Track the canonical scripts/monitor-watchdog.sh (previously only a live crontab copy) and apply the identical fix to the in-repo project-loop-watchdog.sh. Wire the TAP harness into CI. Refs #3780 Co-authored-by: Claude Code <claude-code@anthropic.com>
🔍 Reviewer: CorrectnessVerdict: APPROVE Summary: The fix correctly replaces the meaningless Full reviewCycle 1 (no prior Correctness verdict on this PR) — complete class-labeled change-list. Verified locally (PR head 6c33e2f, already current with
Correctness — no blocking findings:
Non-blocking notes (inline-class, do not gate):
|
🔍 Reviewer: RiskVerdict: APPROVE Summary: Operator crontab tooling and one CI job only — no observable/interop surface (ledger/bucket hashes, tx result/meta XDR, SCP/overlay wire, history-archive format, HTTP/RPC/CLI contract, crypto output) is touched. No parity-critical crate is in the diff. Operational risk is low and net-positive: the change turns a silent 24 h dark-monitoring failure mode into a retrying, escalating one. Full reviewCycle 1 (no prior Risk verdict on this PR) — complete class-labeled change-list. Parity/observable surface: n/a. Changed paths are Operational risk — no blocking findings:
Non-blocking notes (inline-class, do not gate):
CI signal: the only non-green rollup entry is |
Review: Bounce-Back Cycle 1Reason: CI failed (unrelated, will rebase) Reviewer A: APPROVE — post-condition fix is correct; regression harness reproduces the incident and passes 15/15 (verified locally on PR head). Both agent reviewers approve. The sole non-green CI signal is unrelated infra flakiness, not attributable to this diff:
The merge gate still requires a green CI rollup. Routing back to Bounce-back count (head-scoped): 1/3. |
tomerweller
left a comment
There was a problem hiding this comment.
Addressed bounce-back cycle 1. Ready for re-review.
Both agent reviewers (Correctness, Risk) already APPROVED with no requested code changes. The sole non-green signal was test (testnet, core,horizon, …) = CANCELLED — live-testnet infra flakiness unrelated to this shell/CI diff.
Branch is already directly on top of current origin/main (no divergence), so the rebase was a no-op and no code changed. Re-triggered the cancelled Quickstart run (32620216502) on the same head 6c33e2f to clear the flaky testnet probe. A fresh green rollup should now let the merge gate proceed.
The two non-blocking inline notes from the Correctness review (empty-body numeric guard in watchdog_classify_outcome; suspect-fast contributing to failstreak) were explicitly marked "do not gate" and are left as-is to keep the bounced PR scope fixed.
tomerweller
left a comment
There was a problem hiding this comment.
Addressed bounce-back cycle 1 (re-run). Ready for re-review.
Both agent reviewers (Correctness, Risk) already APPROVED with no requested code changes and no inline review comments. Branch is already up to date with origin/main (merge-base == current main 5ac2f16), so no rebase was possible or needed.
The sole non-green CI signal was test (testnet, core,horizon, …) = CANCELLED — live-testnet infra flakiness unrelated to this shell/CI-yaml-only diff. I re-ran the failed Quickstart jobs to clear it. All other checks are green, including the PR's own Watchdog Post-Condition regression harness (15/15 assertions pass locally and in CI).
Review: CI Stuck — Blocked (persistent unrelated testnet cancellation)Reviewer A (Correctness): APPROVE — post-condition fix correct; regression harness reproduces the incident (15/15). Pattern: The only non-green rollup entry is This is the escalation the cycle-1 bounce promised: cycle 1 (2026-08-23T08:18Z) bounced to Operator action required: both reviewer lenses APPROVE and every diff-attributable check is green. The merge is blocked solely on the flaky/timing-out live-testnet |
Closes #3780
Summary
The crontab watchdogs (
monitor-watchdog.sh,project-loop-watchdog.sh) recordedexit=$?fromclaude -pas the tick result, but that is the CLI's exit status, not the tick's. A refusal to run — org monthly spend limit, auth failure — exits 0 and was indistinguishable from a completed tick. On 2026-07-28/29 this produced ~24h of dark mainnet monitoring logged as 49 "successful" ticks, and because the refire-cooldown MARKER was written before the launch, each 1-second no-op burned the full 30-min cooldown.This adds a real post-condition keyed on the only true success signal — a new
tick-history.jsonlrow:scripts/lib/watchdog-postcondition.sh:watchdog_hist_countand a purewatchdog_classify_outcomereturningsuccess(row advanced, duration ≥ floor),suspect-fast(row advanced but under the floor),noop-refusal(no row + known refusal regex), ornoop-empty(no row, no recognized reason).outcome=<class> dur=Ns rows=Nline (preserving the captured CLI output in the log), write the cooldown MARKER only onsuccess(a no-op now retries at the next*/15slot), and emit a greppableESCALATIONline afterESCALATE_AFTER(default 3) consecutive failed launches.scripts/monitor-watchdog.shis now tracked in-repo (previously only a live crontab copy; its own header claimed a canonical repo copy that did not exist).Plan reference
Converged Plan comment
Test plan
bash scripts/test-watchdog-postcondition.sh— 15/15 pass (was 13/15 failing pre-fix)bash scripts/test-project-loop-skill-snippets.sh— unaffected, passesbash -nclean on both watchdogs and the libcargo fmt --checkclean (no Rust files changed).github/workflows/ci.ymlvalid YAML; newwatchdog-postconditionjob wiredNo Rust crate is touched, so
cargo clippy/cargo testcarry no signal for this change and were not run.Regression test (kind: bug-fix)
scripts/test-watchdog-postcondition.sh(test_classify_spend_limit_noop,test_e2e_spend_limit_does_not_burn_cooldown, plus success/empty/suspect-fast + project-loop e2e branches)581ecd4— verified FAILED (13 of 15 assertions: shared lib + canonicalmonitor-watchdog.shabsent;project-loop-watchdog.shlogged bareexit=$rcwith a pre-tick MARKER write, nooutcome=noop-refusal).6c33e2f.Parity considerations
n/a — operator crontab tooling only. No observable/interop surface (ledger/bucket hashes, result/meta XDR, SCP/overlay wire, history-archive format, HTTP/RPC/CLI contract, crypto output) is touched. (Critic B: APPROVE.)
Deviations from plan
REMOTE, offline) beyond the plan's six named tests, so the second modified script's fix is guarded directly rather than only through the shared lib. Per the plan's Critic-A note, the clone path is exercised against a local bare repo under the test's tempdir.Operator action (post-merge, not code)
/home/tomer/data/monitor-watchdog.shand/home/tomer/data/project-loop-watchdog.shfrom the fixed canonical scripts, and copyscripts/lib/watchdog-postcondition.shalongside them (the live copies source it via$SCRIPT_DIR/lib/…, overridable withWATCHDOG_LIB).ESCALATIONline (per Critic C).🤖 Generated with Claude Code