feat: chronic-day escalation + narration-line precision for the logwatch backoff suppressor #349
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: 1 minute 15 seconds
Due date
igor
1 minute 15 seconds
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor!349
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/340-logwatch-de-noise-refinement-chronic-2-day"
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.sh: addlogwatch_record_backoff_day/logwatch_chronic_backoff(distinct-day tracking,.logwatch.backoff_daysstate) andlogwatch_strip_backoff_noise(removes only the backoff's own narration lines from a journal)bin/tick.shdo_logwatch_tick/logwatch_review_unit: replace whole-pass suppression with line-level noise stripping for a transient (1-day) backoff, and skip the stripping once the backoff is chronic (>=2 distinct days) so the recurring pattern stays visible to the reviewerbin/tick.shlogwatch_mark_done: fix a merge bug where the hourly stamp was replacing.logwatchwholesale, which would have wipedbackoff_daysevery hour and made chronic detection unreachableTest plan
make testpasses (full suite, incl. expandedbin/test-logwatch.sh)make lintshellcheck: no new findings introduced (pre-existinglib/cost.shSC2148 unrelated to this change, confirmed present on master before this branch)/security-reviewrun on the diff: no findings (state-only jq writes via--arg/--argjson, hardcoded grep patterns applied to local journal data, no new external input)Closes #340
🤖 Review —
COMMENT(automated)CI for
bdb46c4e: successAdds chronic-day escalation + line-level noise stripping to the logwatch backoff suppressor (lib/logwatch.sh helpers, tick.sh wiring, a mark_done merge fix, and expanded pure-function tests). Well-scoped, honestly described, CI green — but two things I want a human to eyeball before merge.
Checklist honesty: every checked item maps to a real change in the diff (three new lib functions, tick.sh transient/chronic branch, the
.logwatch = ((.logwatch // {}) + {hour})merge fix, expanded tests). The test description is candid that the "full matrix" is exercised against the pure functions with the do_logwatch_tick decision narrated in comments, not end-to-end — acceptable for bash, and it says so rather than overclaiming.Concern 1 — possible abort when the stripped journal is empty (can't verify from the diff).
logwatch_strip_backoff_noiseends ingrep -vE ..., which exits 1 when it filters out every line (the pure-noise case — precisely the transient-suppression happy path). Indo_logwatch_tickthis feedsjournal=$(logwatch_strip_backoff_noise "$journal"). Ifbin/tick.shruns underset -e, that assignment inherits rc=1 and aborts the tick before the intendedif [ -z "$journal" ]skip check ever runs. I can't see tick.sh'ssetflags in this diff, so I can't confirm. If-eis in effect, add a|| true(orreturn 0) so the empty-strip case degrades to the existing skip path. Worth confirming — the tests only call this function inside$(...)in a non--etest harness, so they wouldn't catch it.Concern 2 — distinct-day set is never aged out, only capped.
logwatch_record_backoff_daydedups and keeps the last 30 distinct days (unique | .[-cap:]), but nothing prunes by age. Two isolated blips months apart (e.g. one in January, one in July) will still register as 2 distinct days and trip "chronic," since neither ages out until 30 other distinct days accumulate. The design comment justifies day-granularity vs. timestamp-granularity but doesn't address stale entries surviving long gaps. Probably fine for the intended "recurring" signal, but it means "chronic" can fire on genuinely sparse, unrelated incidents. Flagging for a human call on whether that matters.Nits (non-blocking):
logwatch_record_backoff_day's&& mv ... || rm -f "$tmp"correctly leaves state untouched on jq failure — good, no corruption risk.logwatch: [^:]+: (review call failed|no parseable review)matches any unit name, which is the intended downstream-noise scope; low risk of matching a genuine unrelated failure.Not blocking on the count/scope/security fronts — jq writes use
--arg/--argjson, patterns are hardcoded against local journal data. Just want theset -e/empty-strip interaction confirmed and the stale-day behavior acknowledged before a human merges.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 refinement does exactly what #340 specced and closes the MVP's reset-gap:
.logwatch.backoff_days) survives the.healthreset the MVP's live-state read couldn't — so a chronic backoff is caught even after recovery.do_logwatch_tick: backoff-in-window → record the day; CHRONIC (≥2 distinct days) → narration stays visible → the reviewer files (chronic ≠ noise — your boundary restored); TRANSIENT (first day) → strips only the backoff's own narration lines → a genuine unrelated failure in the same window still files (better than the MVP's suppress-whole-pass).LOGWATCH_CHRONIC_BACKOFF_DAYS=2is a hardcoded constant, not an env knob. ✓logwatch_strip_backoff_noiseremoves only backoff / auth-alert / review-call-failed lines;record_backoff_daypreserves sibling.logwatchkeys (.hoursurvives).Comprehensive test matrix (chronic / transient / dedup / sibling-preservation / strip), CI green, 219 lines. Merge-ready. (Shadow COMMENT, non-blocking.)