pypa/pip · error · ParserSyntaxError

Expected end of marker expression

Error message

Expected end of marker expression

What it means

Raised as a ParserSyntaxError (surfacing as InvalidMarker) at line 315 inside _parse_full_marker: after parsing a complete marker expression via _parse_marker(), tokenizer.expect("END", ...) fails because there is leftover input the marker grammar cannot consume. The parser expected end-of-input but found more tokens.

Solutions

  1. Inspect repr(marker_str) to find the trailing token and remove or properly join it.
  2. Ensure multiple marker conditions are joined with 'and'/'or' and parentheses are balanced.
  3. Validate markers with packaging.markers.Marker() in a test before deployment.

Example fix

// before
Marker('python_version >= "3.0" os_name == "linux"')

// after
Marker('python_version >= "3.0" and os_name == "linux"')
Defensive patterns

Strategy: validation

Validate before calling

from packaging.markers import Marker, InvalidMarker
def valid_marker(s):
    try:
        Marker(s)
        return True
    except InvalidMarker:
        return False

Try / catch

from packaging.markers import Marker, InvalidMarker
try:
    m = Marker(raw)
except InvalidMarker as e:
    raise ValueError(f'bad marker {raw!r}: {e}') from e

Prevention

When it happens

Trigger: A marker string with trailing content after a valid expression, e.g. 'python_version >= "3.0" extra_token', or an unbalanced/extra clause. After _parse_marker returns, the tokenizer is not at END.

Common situations: Manually constructed marker strings with a stray token; concatenating marker fragments incorrectly; a missing boolean operator between two marker items so the second is left dangling.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/04a64b0fc2b3a91f. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/_parser.py:315

        tokenizer.consume("WS")
        if not tokenizer.check("COMMA"):
            break
        parsed_specifiers += tokenizer.read().text
        tokenizer.consume("WS")

    return parsed_specifiers


# --------------------------------------------------------------------------------------
# Recursive descent parser for marker expression
# --------------------------------------------------------------------------------------
def parse_marker(source: str) -> MarkerList:
    return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))


def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
    retval = _parse_marker(tokenizer)
    tokenizer.expect("END", expected="end of marker expression")
    return retval


def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
    """
    marker = marker_atom (BOOLOP marker_atom)+
    """
    expression = [_parse_marker_atom(tokenizer)]
    while tokenizer.check("BOOLOP"):
        token = tokenizer.read()
        expr_right = _parse_marker_atom(tokenizer)
        expression.extend((token.text, expr_right))
    return expression


def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
    """
    marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?

View on GitHub (pinned to f399c37189)