Fix notification dispatch: NameError in _dispatch_to_channels (Quiet Hours)

`_dispatch_to_channels` does NOT receive the NotificationEvent object —
only the rendered primitives (title, body, severity, event_type, …).
The Quiet Hours + Daily Digest merge introduced two references to
`event.severity` / `event` inside this function, which raised
`NameError: name 'event' is not defined` for every event passing
through dispatch.

The dispatch loop swallows the exception with a broad `except`, so the
visible symptom was "the Test button works but no real event ever
arrives" — both for community beta users (multiple reports on
Telegram, 9-18 May) and verified live on a test host (id 905 in
notification_history confirms the pipeline post-fix).

- _dispatch_to_channels: read `severity` / `event_type` directly
  instead of `event.severity` / `event.event_type`.
- _should_buffer_for_digest: take (ch_name, severity, event_type)
  primitives instead of a NotificationEvent.
- _buffer_digest_event: same — take (ch_name, event_type,
  event_group, severity, title, body).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-05-19 00:23:29 +02:00
parent 6eb1312c61
commit 06e6ae417e
3 changed files with 28 additions and 12 deletions
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
70a510025df81652319d16e0d36e77bea95a965163608232e9aca60ada9c9fbf ProxMenux-1.2.1.1-beta.AppImage effc478f957e89f272f9e1bb92ae2eddc4a131eea0b4549eb78477164dd982e9 ProxMenux-1.2.1.1-beta.AppImage
+27 -11
View File
@@ -1167,7 +1167,14 @@ class NotificationManager:
# exploded". CRITICAL always wins. The window is configured # exploded". CRITICAL always wins. The window is configured
# per-channel; same channel can have different rules from # per-channel; same channel can have different rules from
# another. See _in_quiet_hours() for boundary semantics. # another. See _in_quiet_hours() for boundary semantics.
if event.severity != 'CRITICAL' and self._in_quiet_hours(ch_name): # `_dispatch_to_channels` does NOT receive the NotificationEvent
# object — only the rendered primitives. Using `event.X` here
# raised `NameError: name 'event' is not defined` for every
# event passing through (silenced by the dispatch loop's broad
# except → no notifications EVER delivered after Quiet Hours +
# Daily Digest were merged). All community-reported "stopped
# receiving notifications after update" cases trace back here.
if severity != 'CRITICAL' and self._in_quiet_hours(ch_name):
continue continue
# ── Per-channel daily digest ── # ── Per-channel daily digest ──
@@ -1178,8 +1185,9 @@ class NotificationManager:
# and WARNING never wait — they always pass through. Events # and WARNING never wait — they always pass through. Events
# the user explicitly wants to see live (vm_start, etc.) are # the user explicitly wants to see live (vm_start, etc.) are
# excluded from the digest by `_DIGEST_EXEMPT_EVENTS`. # excluded from the digest by `_DIGEST_EXEMPT_EVENTS`.
if self._should_buffer_for_digest(ch_name, event): if self._should_buffer_for_digest(ch_name, severity, event_type):
self._buffer_digest_event(ch_name, event, title, body, event_group) self._buffer_digest_event(ch_name, event_type, event_group,
severity, title, body)
continue continue
try: try:
@@ -1358,23 +1366,31 @@ class NotificationManager:
'system_shutdown', 'system_reboot', 'system_shutdown', 'system_reboot',
}) })
def _should_buffer_for_digest(self, ch_name: str, def _should_buffer_for_digest(self, ch_name: str, severity: str,
event: NotificationEvent) -> bool: event_type: str) -> bool:
"""Decide if this event should go to the channel's digest buffer """Decide if this event should go to the channel's digest buffer
instead of firing immediately. Only applies to generic INFO instead of firing immediately. Only applies to generic INFO
events; CRITICAL and WARNING always pass through. events; CRITICAL and WARNING always pass through.
Takes primitives (severity, event_type) rather than a
NotificationEvent — the caller in `_dispatch_to_channels` only
has the rendered fields, not the event object.
""" """
if event.severity != 'INFO': if severity != 'INFO':
return False return False
if event.event_type in self._DIGEST_EXEMPT_EVENTS: if event_type in self._DIGEST_EXEMPT_EVENTS:
return False return False
if self._config.get(f'{ch_name}.digest_enabled', 'false') != 'true': if self._config.get(f'{ch_name}.digest_enabled', 'false') != 'true':
return False return False
return True return True
def _buffer_digest_event(self, ch_name: str, event: NotificationEvent, def _buffer_digest_event(self, ch_name: str, event_type: str,
title: str, body: str, event_group: str) -> None: event_group: str, severity: str,
"""Append an event to the channel's digest buffer in SQLite.""" title: str, body: str) -> None:
"""Append an event to the channel's digest buffer in SQLite.
Primitives only — same reason as `_should_buffer_for_digest`.
"""
try: try:
conn = sqlite3.connect(str(DB_PATH), timeout=10) conn = sqlite3.connect(str(DB_PATH), timeout=10)
conn.execute('PRAGMA journal_mode=WAL') conn.execute('PRAGMA journal_mode=WAL')
@@ -1383,7 +1399,7 @@ class NotificationManager:
'INSERT INTO digest_pending ' 'INSERT INTO digest_pending '
'(channel, event_type, event_group, severity, ts, title, body) ' '(channel, event_type, event_group, severity, ts, title, body) '
'VALUES (?, ?, ?, ?, ?, ?, ?)', 'VALUES (?, ?, ?, ?, ?, ?, ?)',
(ch_name, event.event_type, event_group, event.severity, (ch_name, event_type, event_group, severity,
int(time.time()), title, body), int(time.time()), title, body),
) )
conn.commit() conn.commit()