Skip to content

fix(ops): one incident is one page — per-unit cooldown on the OnFailure notifier - #432

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/alert-storm-per-unit-cooldown
Aug 28, 2026
Merged

fix(ops): one incident is one page — per-unit cooldown on the OnFailure notifier#432
github-actions[bot] merged 1 commit into
mainfrom
fix/alert-storm-per-unit-cooldown

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

The bombardment

vitareba-app could not read its .env on 2026-08-28 (root-owned after a chown --reference). Restart=on-failure + RestartSec=3 restarted it 18 times in 60 seconds; every restart fired OnFailure=notify-failure@, and every one of those sent its own Telegram — six identical "UNIT DOWN" messages for one incident, none of which made the fix arrive sooner.

The existing guard could not help. It silences a unit that comes back within 8s (deploy restarts, transient blips). A unit that cannot start at all never comes back, so the guard fell through and paged on every loop iteration.

The fix

Per-unit cooldown stamp under $MON/state/paged_<unit>:

  • page once, then hold for NOTIFY_COOLDOWN_SEC (default 30m)
  • still down when it expires → re-page as a reminder
  • recovery clears the stamp, so the next genuine outage pages immediately instead of inheriting the last one's silence
  • a different unit is a different incident — one crash loop cannot mute the fleet
  • suppressed restarts still go to the journal: quiet in Telegram must not mean invisible in the logs

Also fixed

The summary block added in #431 sat in the middle of the test file, so [ "$fail" -eq 0 ] under set -e aborted the run on the first failure and every case below it never executed — the suite reported on a partial run of itself. One summary, at the end.

Verification

scripts/hetzner/test-host-alerts.sh extracts and drives the shipped NF heredoc — no second copy to drift. 33 passed, 0 failed.

Proven by mutation, on a throwaway copy (never the file being pushed), hash-checked before and after:

mutation result
drop the cooldown guard RED — 3 cases
recovery no longer clears the stamp RED — 2 cases
cooldown never expires RED — 1 case

🤖 Generated with Claude Code

https://claude.ai/code/session_012FQCaKPFv3JpN1RyLEXLZ4

…re notifier

vitareba-app could not read its .env on 2026-08-28 (root-owned after a
`chown --reference`). Restart=on-failure + RestartSec=3 restarted it 18 times
in 60 seconds, every restart fired OnFailure, and every OnFailure sent its own
Telegram: six identical "UNIT DOWN" messages for ONE incident, none of which
made the fix arrive any sooner.

The existing guard could not help. It silences a unit that comes BACK within
8s (deploy restarts, transient blips); a unit that cannot start at all never
comes back, so the guard fell through and paged on every loop iteration.

Page once per unit, hold for NOTIFY_COOLDOWN_SEC (30m), then re-page as a
reminder while it is still down. Recovery clears the stamp, so the next
genuine outage pages immediately instead of inheriting the last one's silence.
Suppressed restarts still go to the journal — quiet in Telegram must not mean
invisible in the logs.

Also: the summary block added in #431 sat in the MIDDLE of the test file, so
`[ "$fail" -eq 0 ]` under `set -e` aborted the run on the first failure and
the cases below it never executed — a suite that reports on itself partially.
One summary, at the end, after every case has run.

Covered by scripts/hetzner/test-host-alerts.sh, which drives the shipped
heredoc payload (no second copy to drift). Proven by mutation on a throwaway
copy: dropping the cooldown, dropping the recovery clear, and freezing the
cooldown each turn the suite red.
@github-actions
github-actions Bot merged commit 80fca6c into main Aug 28, 2026
3 checks passed
@github-actions
github-actions Bot deleted the fix/alert-storm-per-unit-cooldown branch August 28, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant