dgtlmoon/changedetection.io · warning · FilterNotFoundInResponse

{self.filter_config.include_filters}

Error message

{self.filter_config.include_filters}

What it means

FilterNotFoundInResponse raised by apply_include_filters in the text_json_diff processor when the combined output of all include filters (CSS/XPath/regex) is empty or whitespace-only. The message embeds the filter list that matched nothing, plus the screenshot and xpath_data for UI display.

Source

Thrown at changedetectionio/processors/text_json_diff/processor.py:382

            # JSON filters
            elif any(filter_rule.startswith(prefix) for prefix in JSON_FILTER_PREFIXES):
                filtered_content += html_tools.extract_json_as_string(
                    content=content,
                    json_filter=filter_rule
                )

            # CSS selectors, default fallback
            else:
                filtered_content += html_tools.include_filters(
                    include_filters=filter_rule,
                    html_content=content,
                    append_pretty_line_formatting=not self.watch.is_source_type_url
                )

        # Raise error if filter returned nothing
        if not filtered_content.strip():
            raise FilterNotFoundInResponse(
                msg=self.filter_config.include_filters,
                screenshot=self.fetcher.screenshot,
                xpath_data=self.fetcher.xpath_data
            )

        return filtered_content

    def apply_subtractive_selectors(self, content):
        """Remove elements matching subtractive selectors."""
        return html_tools.element_removal(self.filter_config.subtractive_selectors, content)

    def extract_text_from_html(self, html_content, stream_content_type):
        """Convert HTML to plain text."""
        do_anchor = self.datastore.data["settings"]["application"].get("render_anchor_tag_content", False)

        return html_tools.html_to_text(
            html_content=html_content,
            render_anchor_tag_content=do_anchor,

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Re-inspect the live page and update the include filter to a stable selector (prefer data-* attributes or ids)
  2. Loosen the filter (e.g. match an ancestor container) or add a fallback filter line
  3. Check the watch's debug/screenshot to see what page was actually fetched — fix bot-blocking first if that's the cause
  4. Remove the include filter entirely if you want whole-page monitoring

Example fix

# before
include_filters: "//span[@class='price-old-2023']"
# after
include_filters: "//span[contains(@class,'price')]"
Defensive patterns

Strategy: try-catch

Validate before calling

# smoke-test the selector before relying on the watch
from lxml import html as lh
doc = lh.fromstring(requests.get(url, timeout=30).content)
if not doc.xpath(filter_string):
    print('filter matches nothing on current page')

Try / catch

try:
    handler.run_changedetection(watch, ...)
except FilterNotFoundInResponse as e:
    notify(f"filter no longer matches: {e}")  # e.msg holds the filter list

Prevention

When it happens

Trigger: Calling a check with include_filters set while every filter expression evaluates to nothing on the current page: selector no longer present, XPath returns an empty node-set, or regex has no match. Note that non-empty whitespace output also triggers it because of the .strip() check.

Common situations: Site redesign removed/renamed the element your selector targets; page served differently to the scraper (bot page, geo variant) so the node is absent; overly specific XPath broken by minor markup changes; regex with a typo or anchored to text that changed.

Related errors


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