{"record":{"id":"04a64b0fc2b3a91f","repo":"pypa/pip","slug":"expected-end-of-marker-expression","errorCode":null,"errorMessage":"Expected end of marker expression","messagePattern":"Expected end of marker expression","errorType":"exception","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":315,"sourceCode":"        tokenizer.consume(\"WS\")\n        if not tokenizer.check(\"COMMA\"):\n            break\n        parsed_specifiers += tokenizer.read().text\n        tokenizer.consume(\"WS\")\n\n    return parsed_specifiers\n\n\n# --------------------------------------------------------------------------------------\n# Recursive descent parser for marker expression\n# --------------------------------------------------------------------------------------\ndef parse_marker(source: str) -> MarkerList:\n    return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))\n\n\ndef _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:\n    retval = _parse_marker(tokenizer)\n    tokenizer.expect(\"END\", expected=\"end of marker expression\")\n    return retval\n\n\ndef _parse_marker(tokenizer: Tokenizer) -> MarkerList:\n    \"\"\"\n    marker = marker_atom (BOOLOP marker_atom)+\n    \"\"\"\n    expression = [_parse_marker_atom(tokenizer)]\n    while tokenizer.check(\"BOOLOP\"):\n        token = tokenizer.read()\n        expr_right = _parse_marker_atom(tokenizer)\n        expression.extend((token.text, expr_right))\n    return expression\n\n\ndef _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:\n    \"\"\"\n    marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/_parser.py#L297-L333","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect repr(marker_str) to find the trailing token and remove or properly join it.","Ensure multiple marker conditions are joined with 'and'/'or' and parentheses are balanced.","Validate markers with packaging.markers.Marker() in a test before deployment."],"exampleFix":"// before\nMarker('python_version >= \"3.0\" os_name == \"linux\"')\n\n// after\nMarker('python_version >= \"3.0\" and os_name == \"linux\"')","handlingStrategy":"validation","validationCode":"from packaging.markers import Marker, InvalidMarker\ndef valid_marker(s):\n    try:\n        Marker(s)\n        return True\n    except InvalidMarker:\n        return False","typeGuard":null,"tryCatchPattern":"from packaging.markers import Marker, InvalidMarker\ntry:\n    m = Marker(raw)\nexcept InvalidMarker as e:\n    raise ValueError(f'bad marker {raw!r}: {e}') from e","preventionTips":["Join multiple marker conditions with explicit 'and'/'or' and balance parentheses.","Validate marker strings with Marker() in tests and inspect the reported error span."],"tags":["packaging","pep508","marker","parser","syntax-error"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}