dgtlmoon/changedetection.io · error · ValueError
Invalid notification format: "{value}"
Error message
Invalid notification format: "{value}" What it means
The same notification dict also validates on every __setitem__: assigning notification_format to an unknown value raises ValueError immediately, unless the value starts with 'RANDOM-PLACEHOLDER-' (used by test fixture filling).
Source
Thrown at changedetectionio/notification_service.py:274
n_format = self.get('notification_format')
if n_format and not valid_notification_formats.get(n_format):
raise ValueError(f'Invalid notification format: "{n_format}"')
def set_random_for_validation(self):
import random, string
"""Randomly fills all dict keys with random strings (for validation/testing).
So we can test the output in the notification body
"""
for key in self.keys():
if key in ['uuid', 'time', 'watch_uuid', 'change_datetime'] or key.startswith('diff'):
continue
rand_str = 'RANDOM-PLACEHOLDER-'+''.join(random.choices(string.ascii_letters + string.digits, k=12))
self[key] = rand_str
def __setitem__(self, key, value):
if key == 'notification_format' and isinstance(value, str) and not value.startswith('RANDOM-PLACEHOLDER-'):
if not valid_notification_formats.get(value):
raise ValueError(f'Invalid notification format: "{value}"')
super().__setitem__(key, value)
def add_rendered_diff_to_notification_vars(notification_scan_text:str, prev_snapshot:str, current_snapshot:str, word_diff:bool, escape_output:bool=False):
"""
Efficiently renders only the diff placeholders that are actually used in the notification text.
Scans the notification template for diff placeholder usage (diff, diff_added, diff_clean, etc.)
and only renders those specific variants, avoiding expensive render_diff() calls for unused placeholders.
Uses LRU caching to avoid duplicate renders when multiple placeholders share the same arguments.
Args:
notification_scan_text: The notification template text to scan for placeholders
prev_snapshot: Previous version of content for diff comparison
current_snapshot: Current version of content for diff comparison
word_diff: Whether to use word-level (True) or line-level (False) diffing
escape_output: If True, the rendered diff output is HTML-escaped. Used for HTML-format
notifications so attacker-controlled page content can't inject live markup.View on GitHub (pinned to 5d9c7c6da7)
Solutions
- Assign only keys present in valid_notification_formats (verify exact casing)
- Validate/normalize user-supplied format strings against valid_notification_formats.keys() before assignment
- When bulk-loading external configs, wrap the update in try/except and coerce or drop invalid formats
Example fix
# before
notification_obj['notification_format'] = fmt_from_user
# after
from changedetectionio.notification_service import valid_notification_formats
if fmt_from_user in valid_notification_formats:
notification_obj['notification_format'] = fmt_from_user
else:
notification_obj['notification_format'] = 'Text' Defensive patterns
Strategy: validation
Validate before calling
from changedetectionio.notification_service import valid_notification_formats
if new_format in valid_notification_formats:
d['notification_format'] = new_format
else:
d.pop('notification_format', None) # fall back to default Type guard
def is_assignable_format(value) -> bool:
from changedetectionio.notification_service import valid_notification_formats
return not isinstance(value, str) or value.startswith('RANDOM-PLACEHOLDER-') or value in valid_notification_formats Try / catch
try:
d['notification_format'] = fmt
except ValueError:
d['notification_format'] = 'Text' Prevention
- Never assign unvalidated user input directly to notification_format
- Watch casing: formats are case-sensitive keys
- When bulk-merging dicts, validate keys against the enum first
When it happens
Trigger: d['notification_format'] = 'html' (wrong case or unofficial name); programmatically copying config between objects where the format string came from user input or an unvalidated source; merge/update of dict-like data into the notification object.
Common situations: Case-sensitivity mistakes ('html' vs 'HTML'); importing watch URLs/JSON from another instance with a format name the current version doesn't recognise; scripts that set the key from unvalidated HTTP input.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Invalid notification format: "{n_format}"
- RegEx '%s' is not a valid regular expression.
- Empty value not allowed.
- Invalid value.
- Bounding box value is too long
AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27).
Data as JSON: /api/errors/c9d7072c7217f66c.
Report an issue: GitHub.