chore: fix(logwatch): suppress ticket filing when a Claude health backoff overlapped the reviewed hour #341
No reviewers
Labels
No labels
Agent
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
2 participants
Notifications
Total time spent: 46 seconds
Due date
igor
46 seconds
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor!341
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/334-logwatch-suppress-tickets-when-a-claude-health"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this PR does
lib/logwatch.shwithlogwatch_health_backoff_in_window(window-overlap check against.health.kind/first_failure/cooldown_until)do_logwatch_tickinbin/tick.sh, before the per-repo discovery loop, so the whole pass (not just filing) is skipped and loggedbin/test-logwatch.shcovering: no state file, healthy state, backoff fully inside the window, backoff straddling either edge, backoff entirely outside the window, and a non-auth/limitkindTest plan
bash bin/check-sync.shpasses (runs everybin/test-*.sh, including the newtest-logwatch.sh)/security-reviewrun on the diff: no findingsCloses #334
🤖 Review —
COMMENT(automated)CI for
b915d360: successAdds
lib/logwatch.shwith a pure window-overlap guard and wires it intodo_logwatch_tickto skip the hourly logwatch pass when a Claude auth/limit health backoff overlapped the reviewed hour. Clean, focused (~110 lines), tests added, CI green, checklist matches the diff. One thing I can't verify from the diff keeps me off APPROVE.Overlap logic is correct.
first_failure < win_end && cooldown_until > win_startis the standard half-open interval overlap for[first_failure, cooldown_until)vs[win_start, win_end). The test matrix (inside / straddle-start / straddle-end / before / after / kind=other / missing file / cleared) exercises each branch and the expected rc's line up. Nice coverage.Window computation is right.
date -d '1 hour ago' ... %H:00:00(prev hour start) →date ... %H:00:00(current hour start) yields the hour that just closed, matching the "reviewed hour" intent.Checklist honesty: every checked box maps to real diff content — the lib helper, the tick.sh wiring before the discovery loop, and the six-case test file are all present.
Open question (why COMMENT, not APPROVE):
return 1semantics on suppression (bin/tick.sh~L2478). The suppression path logs and thenreturn 1afterlogwatch_mark_donealready ran. I can't see the caller ofdo_logwatch_tickor the other early-return paths in this function from the diff, so I can't confirm whether a non-zero return is treated as a benign skip or as a failure (which could trigger an error log, retry, or alert — the opposite of the intended quieting). Please confirm the caller ignores/expects the return code here, or that other intentional-skip paths indo_logwatch_tickalsoreturn 1(consistency). If skips elsewhere usereturn 0, this should probably match.Minor / non-blocking:
logwatch_health_backoff_in_windowassumes.health.first_failure/.cooldown_untilare integers; a non-integer value would make[ "$x" -gt ... ]error underset -u-ish contexts. Given these are epoch seconds written bylib/claude.shthis is almost certainly fine, just noting the implicit contract.No security or scope concerns. Resolve the return-code question and this is APPROVE-ready.
Independent review by the harness on
claude-opus-4-8(effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.CoS binding review: 🟢 GREEN — merge-ready.
Verified the correctness-critical points:
claude_health_blocked✓ — reads raw.healthand testsfirst_failure < win_end && cooldown_until > win_start; the code comment correctly explains why (a backoff may have lifted by the time logwatch reviews the closed hour). Exactly the trap the spec flagged.logwatch_mark_done(hour won't re-run),return 1= suppress-and-continue-cascade, matching the function convention (if do_logwatch_tick; then exit 0).discretionary_state_file()resolves.[1h-ago:00, this-hour:00)= the closed hour logwatch reviews.test-logwatch.shruns in check-sync).Honest caveat (documented in-code, not a blocker): the post-recovery reset gap — once a successful call clears
.health, the overlap history is erased, so a later pass reviewing a backoff-touched window won't suppress. This still covers the real #332/#333 incident (filed during the backoff); #340 (refinement) closes it via durable backoff-day tracking.Merge-ready. (Shadow verdict COMMENT, non-blocking.) Split saga: #334 capped twice at full scope → re-scoped to this MVP, which fit in one attempt.