pypa/pip · error · SyntaxError

error in string literal: %s

Error message

error in string literal: %s

What it means

Raised inside distlib's PEP 508 marker parser (`parse_marker` -> `marker_var`) while consuming a quoted string literal within an environment marker expression. If the next character is not a valid string chunk per the `STRING_CHUNK` regex (e.g. contains a character not allowed in the chunk class), the parser aborts with `SyntaxError: error in string literal`. This is a vendored copy of distlib used by pip for parsing requirement markers like `; python_version >= '3.8'`.

Source

Thrown at src/pip/_vendor/distlib/util.py:89

            raise SyntaxError('unexpected end of input')
        else:
            q = remaining[0]
            if q not in '\'"':
                raise SyntaxError('invalid expression: %s' % remaining)
            oq = '\'"'.replace(q, '')
            remaining = remaining[1:]
            parts = [q]
            while remaining:
                # either a string chunk, or oq, or q to terminate
                if remaining[0] == q:
                    break
                elif remaining[0] == oq:
                    parts.append(oq)
                    remaining = remaining[1:]
                else:
                    m = STRING_CHUNK.match(remaining)
                    if not m:
                        raise SyntaxError('error in string literal: %s' % remaining)
                    parts.append(m.groups()[0])
                    remaining = remaining[m.end():]
            else:
                s = ''.join(parts)
                raise SyntaxError('unterminated string: %s' % s)
            parts.append(q)
            result = ''.join(parts)
            remaining = remaining[1:].lstrip()  # skip past closing quote
        return result, remaining

    def marker_expr(remaining):
        if remaining and remaining[0] == '(':
            result, remaining = marker(remaining[1:].lstrip())
            if remaining[0] != ')':
                raise SyntaxError('unterminated parenthesis: %s' % remaining)
            remaining = remaining[1:].lstrip()
        else:
            lhs, remaining = marker_var(remaining)

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Quote marker string literals with single or double quotes and avoid disallowed characters inside them (notably backslashes and characters outside the chunk class).
  2. Use plain ASCII variable names and values in markers (`python_version`, `sys_platform`, etc.).
  3. Validate the requirement string with `packaging.requirements.Requirement(...)` before passing it to pip.

Example fix

# before
requests==2.28.0 ; python_version >= "3\.8"   # backslash inside literal

# after
requests==2.28.0 ; python_version >= "3.8"
Defensive patterns

Strategy: validation

Validate before calling

from packaging.markers import Marker, InvalidMarker

def validate_marker(req_with_marker: str) -> None:
    # split off the marker after ';'
    if ";" in req_with_marker:
        marker = req_with_marker.split(";", 1)[1].strip()
        try:
            Marker(marker)
        except InvalidMarker as e:
            raise ValueError(f"Bad marker literal: {e}") from e

Type guard

from packaging.markers import Marker, InvalidMarker

def is_valid_marker(m: str) -> bool:
    try:
        Marker(m)
        return True
    except InvalidMarker:
        return False

Try / catch

null

Prevention

When it happens

Trigger: `parse_marker(marker_string)` is called (transitively when pip parses a requirement with a marker) and a quoted literal in the marker contains a character outside the `STRING_CHUNK` allowed set `[^\s\w.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]` (e.g. a backslash, an emoji, or an unmatched quote inside the string).

Common situations: Typos in requirement markers in `requirements.txt`; copy-pasting markers from documentation that use smart quotes; embedding invalid characters; a stray `\` or unescaped quote inside the marker string literal.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/dfc1bb13b528e477.json. Report an issue: GitHub.