{"id":"05bfc34a1e2f294e","repo":"pypa/pip","slug":"unterminated-parenthesis-s","errorCode":null,"errorMessage":"unterminated parenthesis: %s","messagePattern":"unterminated parenthesis: (.+?)","errorType":"validation","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":104,"sourceCode":"                else:\n                    m = STRING_CHUNK.match(remaining)\n                    if not m:\n                        raise SyntaxError('error in string literal: %s' % remaining)\n                    parts.append(m.groups()[0])\n                    remaining = remaining[m.end():]\n            else:\n                s = ''.join(parts)\n                raise SyntaxError('unterminated string: %s' % s)\n            parts.append(q)\n            result = ''.join(parts)\n            remaining = remaining[1:].lstrip()  # skip past closing quote\n        return result, remaining\n\n    def marker_expr(remaining):\n        if remaining and remaining[0] == '(':\n            result, remaining = marker(remaining[1:].lstrip())\n            if remaining[0] != ')':\n                raise SyntaxError('unterminated parenthesis: %s' % remaining)\n            remaining = remaining[1:].lstrip()\n        else:\n            lhs, remaining = marker_var(remaining)\n            while remaining:\n                m = MARKER_OP.match(remaining)\n                if not m:\n                    break\n                op = m.groups()[0]\n                remaining = remaining[m.end():]\n                rhs, remaining = marker_var(remaining)\n                lhs = {'op': op, 'lhs': lhs, 'rhs': rhs}\n            result = lhs\n        return result, remaining\n\n    def marker_and(remaining):\n        lhs, remaining = marker_expr(remaining)\n        while remaining:\n            m = AND.match(remaining)","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L86-L122","documentation":"Raised by distlib's marker parser inside `marker_expr` when a parenthesised sub-expression was opened with `(` but the matching `)` is never found before input ends. The parser checks `remaining[0] != ')'` after recursing and raises `SyntaxError: unterminated parenthesis`.","triggerScenarios":"Parsing a marker like `(python_version >= '3.8' and os_name == 'posix'` (no closing paren) with `parse_marker`. Triggered when pip parses a requirement whose marker groups sub-conditions with parens but omits the closer.","commonSituations":"Hand-written complex markers in `requirements.txt`/`pyproject.toml`; refactoring a marker and dropping a `)`; line wrapping that lost the trailing paren.","solutions":["Balance the parentheses: every `(` must have a matching `)`.","Simplify the marker or split it into multiple requirements to avoid grouping.","Validate the marker with `packaging.markers.Marker(...)` before committing the requirements file."],"exampleFix":"# before\nrequests==2.28.0 ; (python_version >= '3.8' and platform_system == 'Linux'\n\n# after\nrequests==2.28.0 ; (python_version >= '3.8' and platform_system == 'Linux')","handlingStrategy":"validation","validationCode":"from packaging.markers import Marker, InvalidMarker\n\ndef validate_marker_parens(marker: str) -> None:\n    try:\n        Marker(marker)\n    except InvalidMarker as e:\n        raise ValueError(f\"Marker parse error: {e}\") from e","typeGuard":"def marker_parens_balanced(m: str) -> bool:\n    return m.count(\"(\") == m.count(\")\")","tryCatchPattern":"null","preventionTips":["Balance every `(` with a matching `)` in markers.","Prefer simple markers over deeply grouped ones.","Validate with `packaging.markers.Marker(...)`."],"tags":["distlib","pep508","marker","requirements-parsing"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}