dgtlmoon/changedetection.io · warning · ValidationError

'%(expression)s' is not a valid XPath expression. (%(error)s

Error message

'%(expression)s' is not a valid XPath expression. (%(error)s)

What it means

When XPath is allowed and the line (after stripping 'xpath:') is tested, the validator builds an empty HTML tree and runs elementpath.select(tree, expr, parser=SafeXPath3Parser) under an lxml lock. An elementpath.ElementPathError means the expression is syntactically invalid XPath; the original expression and parser error string are interpolated into the ValidationError message.

Source

Thrown at changedetectionio/forms.py:709

                return

            # Does it look like XPath?
            if line.strip()[0] == '/' or line.strip().startswith('xpath:'):
                if not self.allow_xpath:
                    raise ValidationError("XPath not permitted in this field!")
                from lxml import etree, html
                import elementpath
                from changedetectionio.html_tools import SafeXPath3Parser, lxml_guard, lxml_html_parser
                line = line.replace('xpath:', '')

                try:
                    # Runs on a Flask request thread - must share the worker's lxml lock.
                    with lxml_guard():
                        tree = html.fromstring("<html></html>", parser=lxml_html_parser())
                        elementpath.select(tree, line.strip(), parser=SafeXPath3Parser)
                except elementpath.ElementPathError as e:
                    message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
                    raise ValidationError(message % {'expression': line, 'error': str(e)})
                except:
                    raise ValidationError("A system-error occurred when validating your XPath expression")

            if line.strip().startswith('xpath1:'):
                if not self.allow_xpath:
                    raise ValidationError("XPath not permitted in this field!")
                from lxml import etree, html
                from changedetectionio.html_tools import lxml_guard, lxml_html_parser
                line = re.sub(r'^xpath1:', '', line)

                try:
                    # Runs on a Flask request thread - must share the worker's lxml lock.
                    with lxml_guard():
                        tree = html.fromstring("<html></html>", parser=lxml_html_parser())
                        tree.xpath(line.strip())
                except etree.XPathEvalError as e:
                    message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
                    raise ValidationError(message % {'expression': line, 'error': str(e)})

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Test the expression in a browser console or an XPath tester with XPath 3.0 semantics
  2. Fix the reported syntax error (balance [] () "" and correct axis/step names)
  3. Prefer simple axes like //tag[@attr='value'] which parse under any version

Example fix

# before
xpath://div[[text()='hi']
# after
xpath://div[text()='hi']
Defensive patterns

Strategy: validation

Validate before calling

from lxml import html
import elementpath
from changedetectionio.html_tools import SafeXPath3Parser

def xpath_ok(expr: str) -> tuple[bool, str]:
    try:
        tree = html.fromstring('<html></html>')
        elementpath.select(tree, expr, parser=SafeXPath3Parser)
        return True, ''
    except elementpath.ElementPathError as e:
        return False, str(e)
    except Exception as e:
        return False, f'system error: {e}'

Try / catch

try:
    elementpath.select(tree, expr, parser=SafeXPath3Parser)
except elementpath.ElementPathError as e:
    flash(f"'{expr}' is not a valid XPath expression. ({e})")

Prevention

When it happens

Trigger: A line starting with '/' or 'xpath:' containing malformed XPath 3.0 syntax — unbalanced brackets, invalid axis names, bad predicates — e.g. 'xpath://div[[text()' raises ElementPathError during elementpath.select and this ValidationError is returned.

Common situations: Typos in axes or predicates, XPath 1.0-only habits clashing with the XPath 3.0 parser, unclosed quote inside a predicate, or copying browser $x() expressions with extra wrapping.

Related errors


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