fix(alert-monitor): stop over-suppressing alerts in the noise log (#326) #517

Merged
k.renner merged 1 commits from fix-326-noise-log-suppression into master 2026-08-19 10:54:28 +00:00
Member

Refs #326. That issue is the alert-monitor suppression state and stays open by design — this fixes the MECHANISM that maintains it, not the issue.

Two defects, both of which silently mute real alerts.

1. Fingerprints mentioned in prose became suppressions

The dedup step grepped the ENTIRE issue body for 16-hex tokens, so triage reasons that cite another alert fingerprint in prose ("see the issue filed for fingerprint X") were read as suppression entries.

Verified against the live noise log: the old extractor yields 22 fingerprints for 21 real bullets. The extra one is 2ddc41edb689fc33 — the crowdsec alert that HAD been filed as a genuine issue, permanently muted purely because two other entries mention it in their reason text.

Now only lines in the exact shape the write-back step emits count:

- <fp> <ALERTNAME> — <YYYY-MM-DD>: <reason>

Re-verified with the new parser: 21, and 2ddc41edb689fc33 is no longer among them.

2. Suppression never expired

The fingerprint is sha256(alertname|scope) and is deliberately stable across recurrences. Combined with a permanent noise log, one blip verdict mutes that alertname+scope pair FOREVER.

21 pairs are currently muted that way, including KubePodNotReady, KubeStatefulSetReplicasMismatch and KubePdbNotEnoughHealthyPods for openbao — all recorded as "node reboot evicted openbao-0, self-resolved". As it stands, a real openbao outage would file no issue at all.

A noise verdict means "this occurrence was benign", not "this alert can never be interesting again". Entries now expire after NOISE_TTL_DAYS (default 30), counted from the date already in each line. If it is still noise, triage re-adds it automatically.

Rollout is gentle by construction

Every current entry is dated 2026-07-21 or later, so at a 30-day TTL the cutoff is 2026-07-20 and NOTHING un-mutes on merge. The oldest entries lapse a day later, and only get re-triaged if they actually fire again.

Unparseable lines are dropped — they stop suppressing. This fails toward MORE alerting, never less.

Verification

Ran the exact shipped snippet inside the real ci-claude:2.1.218 image against the live noise log:

cutoff=2026-07-20
suppressions=21
prose-only fp 2ddc41edb689fc33 present? false

Expiry exercised at several cutoffs: 2026-07-20 -> 21 kept, 2026-07-25 -> 5 kept.

Worth knowing: that image ships BusyBox date, not GNU — date -d "30 days ago" fails there outright. The cutoff therefore uses epoch arithmetic plus date -d @<epoch>, confirmed working in that image. BusyBox sed -E and awk gsub were confirmed the same way, by running the snippet in-image rather than trusting local GNU tools.

woodpecker lint passes.

🤖 Generated with Claude Code

Refs #326. That issue is the alert-monitor suppression state and stays open by design — this fixes the MECHANISM that maintains it, not the issue. Two defects, both of which silently mute real alerts. ## 1. Fingerprints mentioned in prose became suppressions The dedup step grepped the ENTIRE issue body for 16-hex tokens, so triage reasons that cite another alert fingerprint in prose ("see the issue filed for fingerprint X") were read as suppression entries. Verified against the live noise log: the old extractor yields **22** fingerprints for **21** real bullets. The extra one is `2ddc41edb689fc33` — the crowdsec alert that HAD been filed as a genuine issue, permanently muted purely because two other entries mention it in their reason text. Now only lines in the exact shape the write-back step emits count: ``` - <fp> <ALERTNAME> — <YYYY-MM-DD>: <reason> ``` Re-verified with the new parser: **21**, and `2ddc41edb689fc33` is no longer among them. ## 2. Suppression never expired The fingerprint is `sha256(alertname|scope)` and is deliberately stable across recurrences. Combined with a permanent noise log, one blip verdict mutes that alertname+scope pair FOREVER. 21 pairs are currently muted that way, including `KubePodNotReady`, `KubeStatefulSetReplicasMismatch` and `KubePdbNotEnoughHealthyPods` for **openbao** — all recorded as "node reboot evicted openbao-0, self-resolved". As it stands, a real openbao outage would file no issue at all. A noise verdict means "this occurrence was benign", not "this alert can never be interesting again". Entries now expire after `NOISE_TTL_DAYS` (default 30), counted from the date already in each line. If it is still noise, triage re-adds it automatically. ### Rollout is gentle by construction Every current entry is dated `2026-07-21` or later, so at a 30-day TTL the cutoff is `2026-07-20` and NOTHING un-mutes on merge. The oldest entries lapse a day later, and only get re-triaged if they actually fire again. Unparseable lines are dropped — they stop suppressing. This fails toward MORE alerting, never less. ## Verification Ran the exact shipped snippet inside the real `ci-claude:2.1.218` image against the live noise log: ``` cutoff=2026-07-20 suppressions=21 prose-only fp 2ddc41edb689fc33 present? false ``` Expiry exercised at several cutoffs: `2026-07-20` -> 21 kept, `2026-07-25` -> 5 kept. **Worth knowing:** that image ships BusyBox `date`, not GNU — `date -d "30 days ago"` fails there outright. The cutoff therefore uses epoch arithmetic plus `date -d @<epoch>`, confirmed working in that image. BusyBox `sed -E` and `awk gsub` were confirmed the same way, by running the snippet in-image rather than trusting local GNU tools. `woodpecker lint` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-08-19 08:13:20 +00:00
fix(alert-monitor): stop over-suppressing alerts in the noise log
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
8a8145011a
Two defects in the noise-log dedup, both of which silently mute real alerts.

1. Prose-mentioned fingerprints became suppressions.

The extractor grepped the WHOLE issue body for 16-hex tokens. Triage reasons
routinely cite another alert's fingerprint in prose ("see the issue filed for
fingerprint <fp>"), so those citations were read as suppression entries.

Verified against the live noise log: the old extractor yields 22 fingerprints
for 21 real entries. The extra one is 2ddc41edb689fc33 -- the crowdsec alert
that HAD been filed as a genuine issue, permanently muted purely because two
other entries mention it. Now only lines in the exact shape the write-back step
emits count:

  - <fp> <ALERTNAME> — <YYYY-MM-DD>: <reason>

Re-verified with the new parser: 21, and 2ddc41edb689fc33 is no longer among
them.

2. Suppression never expired.

The fingerprint is sha256(alertname|scope) and is deliberately stable across
recurrences, so a single blip verdict muted that alertname+scope pair forever.
21 pairs were permanently muted, including openbao's KubePodNotReady,
KubeStatefulSetReplicasMismatch and KubePdbNotEnoughHealthyPods -- so a real
openbao outage would have filed no issue at all.

A noise verdict means "this occurrence was benign", not "this alert can never be
interesting again". Entries now expire after NOISE_TTL_DAYS (default 30) counted
from the date already present in each line. Re-triaging a recurrence weeks later
is cheap; missing an outage because a July blip muted it is not. If it is still
noise, triage re-adds it automatically.

Rollout is gentle by construction: every current entry is dated 2026-07-21 or
later, so at a 30d TTL the cutoff is 2026-07-20 and NOTHING un-mutes on merge --
the oldest entries lapse a day later, and only re-triage if they actually fire
again.

Unparseable lines are dropped, i.e. they stop suppressing: this fails toward
more alerting, never less.

Verified end-to-end by running the shipped snippet inside the real
ci-claude:2.1.218 image against the live noise log: cutoff 2026-07-20,
21 suppressions, prose-only fingerprint absent. Note that image ships BusyBox
date, not GNU -- `date -d '30 days ago'` errors there, so the cutoff uses epoch
arithmetic with `date -d @<epoch>`, which was confirmed working in that image.

Refs #326

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review-bot left a comment
Member

🤖 Automated review — model claude-sonnet-5 (complexity: normal), commit 8a8145011a54d4f2de6db097aba48cc9b8081c12.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR fixes two real defects in .woodpecker/alert-monitor.yml's noise-log suppression logic: (1) the fingerprint extractor used to grep the entire noise-log issue body for any 16-hex token, so a fingerprint merely mentioned in prose (e.g. "see the issue filed for fingerprint X") was silently treated as a suppression; (2) suppressions never expired, so a single "blip, self-resolved" verdict permanently muted that alertname+scope pair. I traced the new sed/awk pipeline against the exact write-back format (.woodpecker/alert-monitor.yml:279) by hand and it parses correctly: it only matches lines shaped exactly like - <fp> <ALERTNAME> — <YYYY-MM-DD>: <reason>, a bare fingerprint in reason prose is correctly ignored, and the TTL cutoff comparison (date→YYYYMMDD integer compare) is correct. I also confirmed byte-for-byte that the em dash (U+2014, e2 80 94) used in the new regex (line 165) and the one emitted by the existing printf at line 279 are identical, so the anchor character matches. No blocking issues found.

🟡 Suggestions

  • No automated test for the new parsing logic (.woodpecker/alert-monitor.yml:162-169). This repo has an established pattern for exactly this situation — scripts/ci/test-model-selection.sh + .woodpecker/ci-scripts-test.yml — extracting nontrivial embedded CI shell/regex logic into a standalone script with an offline unit test that runs on every PR. This change adds a sed -nE/awk regex-and-date-arithmetic pipeline of similar complexity (and similar blast radius — a subtly wrong regex here silently drops or keeps suppressions) but leaves it inline in the YAML with only manual, non-repeatable verification described in the PR body. Extracting it to scripts/ci/ with a test using canned noise-log bodies (a prose-only fingerprint mention, an expired entry, a boundary-date entry) would catch a future regression the way test-model-selection.sh does for pick-model.sh.
  • Noise log grows unboundedly and can accumulate duplicate fingerprint lines (.woodpecker/alert-monitor.yml:279-291). Expiry only affects which fingerprints are treated as currently suppressing — expired lines are never removed from the issue body itself. If a fingerprint's suppression expires and it's later re-triaged as noise again, a second line for the same fp/alertname gets appended rather than the old one being replaced, so the body accumulates stale, superseded entries indefinitely. This doesn't cause incorrect behavior (a duplicate fp in the FPS array is harmless for the IN(...) membership check), but it means the "delete a line to un-suppress" maintenance instructions in the header (line 293) become less reliable over time since there could be multiple lines for the same alert. Worth a follow-up to prune/replace on write-back, or at least noting it's out of scope.

🟢 Nits

  • The BusyBox date -d @<epoch> behavior (.woodpecker/alert-monitor.yml:163) is confirmed working per the PR description's in-image verification, but that verification isn't captured as a repo-checked-in test (ties into the suggestion above) — if the ci-claude base image ever changes its date/busybox build, this would fail silently only at pipeline-run time.

Tests

No automated tests are added. The PR body documents thorough manual verification (fingerprint counts before/after, TTL cutoff behavior at two dates, BusyBox-specific tool behavior) run against the live noise log inside the actual ci-claude:2.1.218 image, which is credible and specific. I independently re-derived the regex/awk behavior against synthetic noise-log lines matching the real write-back format and confirmed it distinguishes a fingerprint mentioned only in prose from one in a genuine suppression line, and correctly ages out an entry past the TTL cutoff. Given this script isn't easily exercised by this review's checkout (no live Gitea issue, no cluster), the manual verification is a reasonable substitute for merge, but see the suggestion above about codifying it as a regression test.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `claude-sonnet-5` (complexity: normal), commit `8a8145011a54d4f2de6db097aba48cc9b8081c12`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR fixes two real defects in `.woodpecker/alert-monitor.yml`'s noise-log suppression logic: (1) the fingerprint extractor used to grep the entire noise-log issue body for any 16-hex token, so a fingerprint merely *mentioned in prose* (e.g. "see the issue filed for fingerprint X") was silently treated as a suppression; (2) suppressions never expired, so a single "blip, self-resolved" verdict permanently muted that `alertname+scope` pair. I traced the new `sed`/`awk` pipeline against the exact write-back format (`.woodpecker/alert-monitor.yml:279`) by hand and it parses correctly: it only matches lines shaped exactly like `- <fp> <ALERTNAME> — <YYYY-MM-DD>: <reason>`, a bare fingerprint in reason prose is correctly ignored, and the TTL cutoff comparison (date→`YYYYMMDD` integer compare) is correct. I also confirmed byte-for-byte that the em dash (`U+2014`, `e2 80 94`) used in the new regex (line 165) and the one emitted by the existing `printf` at line 279 are identical, so the anchor character matches. No blocking issues found. ## 🟡 Suggestions - **No automated test for the new parsing logic** (`.woodpecker/alert-monitor.yml:162-169`). This repo has an established pattern for exactly this situation — `scripts/ci/test-model-selection.sh` + `.woodpecker/ci-scripts-test.yml` — extracting nontrivial embedded CI shell/regex logic into a standalone script with an offline unit test that runs on every PR. This change adds a `sed -nE`/`awk` regex-and-date-arithmetic pipeline of similar complexity (and similar blast radius — a subtly wrong regex here silently drops or keeps suppressions) but leaves it inline in the YAML with only manual, non-repeatable verification described in the PR body. Extracting it to `scripts/ci/` with a test using canned noise-log bodies (a prose-only fingerprint mention, an expired entry, a boundary-date entry) would catch a future regression the way `test-model-selection.sh` does for `pick-model.sh`. - **Noise log grows unboundedly and can accumulate duplicate fingerprint lines** (`.woodpecker/alert-monitor.yml:279-291`). Expiry only affects which fingerprints are treated as *currently suppressing* — expired lines are never removed from the issue body itself. If a fingerprint's suppression expires and it's later re-triaged as noise again, a second line for the same fp/alertname gets appended rather than the old one being replaced, so the body accumulates stale, superseded entries indefinitely. This doesn't cause incorrect behavior (a duplicate fp in the `FPS` array is harmless for the `IN(...)` membership check), but it means the "delete a line to un-suppress" maintenance instructions in the header (line 293) become less reliable over time since there could be multiple lines for the same alert. Worth a follow-up to prune/replace on write-back, or at least noting it's out of scope. ## 🟢 Nits - The BusyBox `date -d @<epoch>` behavior (`.woodpecker/alert-monitor.yml:163`) is confirmed working per the PR description's in-image verification, but that verification isn't captured as a repo-checked-in test (ties into the suggestion above) — if the `ci-claude` base image ever changes its `date`/`busybox` build, this would fail silently only at pipeline-run time. ## Tests No automated tests are added. The PR body documents thorough manual verification (fingerprint counts before/after, TTL cutoff behavior at two dates, BusyBox-specific tool behavior) run against the live noise log inside the actual `ci-claude:2.1.218` image, which is credible and specific. I independently re-derived the regex/awk behavior against synthetic noise-log lines matching the real write-back format and confirmed it distinguishes a fingerprint mentioned only in prose from one in a genuine suppression line, and correctly ages out an entry past the TTL cutoff. Given this script isn't easily exercised by this review's checkout (no live Gitea issue, no cluster), the manual verification is a reasonable substitute for merge, but see the suggestion above about codifying it as a regression test. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=8a8145011a54d4f2de6db097aba48cc9b8081c12 -->
k.renner merged commit d437775319 into master 2026-08-19 10:54:28 +00:00
Sign in to join this conversation.