netdata/netdata · error · ScannerError

expected a comment or a line break, but found %r

Error message

expected a comment or a line break, but found %r

What it means

Raised by scan_directive_ignored_line: after a directive's value, only spaces, an optional '#' comment, and a line break may follow. Any other character — e.g. a stray token after '%YAML 1.1' or after a %TAG prefix — triggers this while scanning the directive. It is the generic 'garbage at end of directive line' guard.

Source

Thrown at src/collectors/python.d.plugin/python_modules/pyyaml3/scanner.py:896

    def scan_tag_directive_prefix(self, start_mark):
        # See the specification for details.
        value = self.scan_tag_uri('directive', start_mark)
        ch = self.peek()
        if ch not in '\0 \r\n\x85\u2028\u2029':
            raise ScannerError("while scanning a directive", start_mark,
                    "expected ' ', but found %r" % ch, self.get_mark())
        return value

    def scan_directive_ignored_line(self, start_mark):
        # See the specification for details.
        while self.peek() == ' ':
            self.forward()
        if self.peek() == '#':
            while self.peek() not in '\0\r\n\x85\u2028\u2029':
                self.forward()
        ch = self.peek()
        if ch not in '\0\r\n\x85\u2028\u2029':
            raise ScannerError("while scanning a directive", start_mark,
                    "expected a comment or a line break, but found %r"
                        % ch, self.get_mark())
        self.scan_line_break()

    def scan_anchor(self, TokenClass):
        # The specification does not restrict characters for anchors and
        # aliases. This may lead to problems, for instance, the document:
        #   [ *alias, value ]
        # can be interpteted in two ways, as
        #   [ "value" ]
        # and
        #   [ *alias , "value" ]
        # Therefore we restrict aliases to numbers and ASCII letters.
        start_mark = self.get_mark()
        indicator = self.peek()
        if indicator == '*':
            name = 'alias'
        else:

View on GitHub (pinned to 4864de85e2)

Solutions

  1. Delete everything after the directive value or convert it to a '#' comment on that line.
  2. Confirm the directive line ends with a newline and nothing else.
  3. Re-parse the document to verify the scanner passes the header.

Example fix

# before
%YAML 1.1 default encoding
---
a: 1

# after
%YAML 1.1  # default encoding
---
a: 1
Defensive patterns

Strategy: validation

Validate before calling

import re

def directive_lines_clean(text):
    for l in text.splitlines():
        if l.startswith('%YAML'):
            body = re.sub(r'\s*#.*$', '', l).rstrip()
            if re.fullmatch(r'%YAML\s+\d+\.\d+', body) is None:
                return False
        elif l.startswith('%TAG'):
            body = re.sub(r'\s*#.*$', '', l).rstrip()
            if re.fullmatch(r'%TAG\s+\S+\s+\S+', body) is None:
                return False
    return True

Type guard

def is_ignored_line_error(exc):
    return isinstance(exc, yaml.scanner.ScannerError) and 'comment or a line break' in str(exc)

Try / catch

try:
    yaml.safe_load(text)
except yaml.scanner.ScannerError as e:
    report('directive line has trailing garbage; end it after the value')

Prevention

When it happens

Trigger: yaml.safe_load() on a file where a '%' directive line ends with non-comment, non-break content such as '%YAML 1.1 foo' or '%TAG ! !x 1'.

Common situations: Notes typed after directives assuming free text is allowed; sed/patch operations appending text to header lines without a newline.

Related errors


AI-assisted analysis of netdata/netdata@4864de85e2 (2026-08-15). Data as JSON: /api/errors/b3bbda0cce3eb245. Report an issue: GitHub.