From afd5060f59ca4b90a6af22590caecfa810afe064 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Wed, 22 Apr 2026 18:11:22 +0200 Subject: [PATCH] Fix early reminder notifications and lost recovery notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AlertState.update() now resets last_notification when the alert level changes, so a WARNING→CRITICAL escalation restarts the reminder interval rather than inheriting a nearly-expired timer. - _dispatch_to_channel() bypasses min_level for RECOVER, so recovery notifications are delivered even after a server restart when _alerted_channels is empty and the fallback dispatch path is used. Co-Authored-By: Claude Sonnet 4.6 --- hbd/server/notify.py | 21 ++++++++++++++------- hbd/server/threshold.py | 1 + 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/hbd/server/notify.py b/hbd/server/notify.py index 03957f0..172b3f2 100644 --- a/hbd/server/notify.py +++ b/hbd/server/notify.py @@ -385,13 +385,20 @@ _DRIVERS = { def _dispatch_to_channel(channel_name: str, channel_cfg: dict, notif: Notification) -> bool: - """Send *notif* to a single named channel, honouring min_level.""" - min_level = channel_cfg.get("min_level", "WARNING").upper() - if _level_value(notif.level) < _level_value(min_level): - logger.debug( - "channel '%s': skipping level %s (min_level=%s)", channel_name, notif.level, min_level - ) - return True # not an error — filtered intentionally + """Send *notif* to a single named channel, honouring min_level. + + RECOVER always bypasses min_level — a recovery is always relevant if the + channel was configured for any alerting (handles the restart-then-recover case + where _alerted_channels is empty and we fall through to the normal loop). + """ + level = notif.level.upper() + if level != "RECOVER": + min_level = channel_cfg.get("min_level", "WARNING").upper() + if _level_value(level) < _level_value(min_level): + logger.debug( + "channel '%s': skipping level %s (min_level=%s)", channel_name, level, min_level + ) + return True # not an error — filtered intentionally ch_type = channel_cfg.get("type", "") driver = _DRIVERS.get(ch_type) diff --git a/hbd/server/threshold.py b/hbd/server/threshold.py index 2e6e25a..5496be5 100644 --- a/hbd/server/threshold.py +++ b/hbd/server/threshold.py @@ -105,6 +105,7 @@ class AlertState: self.level = level self.since = now self.notification_count = 0 + self.last_notification = None # restart reminder interval on level change # Reset acknowledgment on state change if level != AlertLevel.OK: # Only reset if changing to a different alert level