{"id":"7318f0a22c7079ea","repo":"pypa/pip","slug":"unterminated-extra-s","errorCode":null,"errorMessage":"unterminated extra: %s","messagePattern":"unterminated extra: (.+?)","errorType":"validation","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":161,"sourceCode":"\ndef parse_requirement(req):\n    \"\"\"\n    Parse a requirement passed in as a string. Return a Container\n    whose attributes contain the various parts of the requirement.\n    \"\"\"\n    remaining = req.strip()\n    if not remaining or remaining.startswith('#'):\n        return None\n    m = IDENTIFIER.match(remaining)\n    if not m:\n        raise SyntaxError('name expected: %s' % remaining)\n    distname = m.groups()[0]\n    remaining = remaining[m.end():]\n    extras = mark_expr = versions = uri = None\n    if remaining and remaining[0] == '[':\n        i = remaining.find(']', 1)\n        if i < 0:\n            raise SyntaxError('unterminated extra: %s' % remaining)\n        s = remaining[1:i]\n        remaining = remaining[i + 1:].lstrip()\n        extras = []\n        while s:\n            m = IDENTIFIER.match(s)\n            if not m:\n                raise SyntaxError('malformed extra: %s' % s)\n            extras.append(m.groups()[0])\n            s = s[m.end():]\n            if not s:\n                break\n            if s[0] != ',':\n                raise SyntaxError('comma expected in extras: %s' % s)\n            s = s[1:].lstrip()\n        if not extras:\n            extras = None\n    if remaining:\n        if remaining[0] == '@':","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L143-L179","documentation":"Raised by distlib's `parse_requirement` when a requirement includes an extras section (`name[...]`) whose opening `[` has no matching `]` before end of input. The parser scans for `]` via `remaining.find(']', 1)` and if not found raises `SyntaxError: unterminated extra`.","triggerScenarios":"Parsing a requirement like `requests[security` (no closing bracket) with `parse_requirement`. Triggered when pip parses a requirement whose extras list is not closed.","commonSituations":"Typing `pkg[extra1,extra2` and forgetting the `]`; shell/quote stripping the `]`; copy-paste truncation; requirements files edited by hand.","solutions":["Close the extras bracket: `pkg[extra1,extra2]`.","Quote the requirement in the shell to prevent `]` from being interpreted/stripped.","Validate with `packaging.requirements.Requirement(...)` before use."],"exampleFix":"# before\nrequests[security\n\n# after\nrequests[security]","handlingStrategy":"validation","validationCode":"from packaging.requirements import Requirement, InvalidRequirement\n\ndef validate_requirement_extras(line: str) -> None:\n    try:\n        Requirement(line)\n    except InvalidRequirement as e:\n        raise ValueError(f\"Bad requirement: {e}\") from e","typeGuard":"def extras_bracket_closed(line: str) -> bool:\n    if \"[\" not in line:\n        return True\n    return line.count(\"[\") == line.count(\"]\")","tryCatchPattern":"null","preventionTips":["Close every `[` in an extras list with `]`.","Shell-quote requirements to prevent `]` stripping.","Lint requirements files for balanced brackets."],"tags":["distlib","requirements-parsing","extras"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}