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

  1. Assign only keys present in valid_notification_formats (verify exact casing)
  2. Validate/normalize user-supplied format strings against valid_notification_formats.keys() before assignment
  3. 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

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


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/c9d7072c7217f66c. Report an issue: GitHub.