pypa/pip · error · SyntaxError
unterminated parenthesis: %s
Error message
unterminated parenthesis: %s
What it means
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`.
Source
Thrown at src/pip/_vendor/distlib/util.py:104
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)
while remaining:
m = MARKER_OP.match(remaining)
if not m:
break
op = m.groups()[0]
remaining = remaining[m.end():]
rhs, remaining = marker_var(remaining)
lhs = {'op': op, 'lhs': lhs, 'rhs': rhs}
result = lhs
return result, remaining
def marker_and(remaining):
lhs, remaining = marker_expr(remaining)
while remaining:
m = AND.match(remaining)View on GitHub (pinned to d7d0d0a394)
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.
Example fix
# before requests==2.28.0 ; (python_version >= '3.8' and platform_system == 'Linux' # after requests==2.28.0 ; (python_version >= '3.8' and platform_system == 'Linux')
Defensive patterns
Strategy: validation
Validate before calling
from packaging.markers import Marker, InvalidMarker
def validate_marker_parens(marker: str) -> None:
try:
Marker(marker)
except InvalidMarker as e:
raise ValueError(f"Marker parse error: {e}") from e Type guard
def marker_parens_balanced(m: str) -> bool:
return m.count("(") == m.count(")") Try / catch
null
Prevention
- Balance every `(` with a matching `)` in markers.
- Prefer simple markers over deeply grouped ones.
- Validate with `packaging.markers.Marker(...)`.
When it happens
Trigger: 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.
Common situations: Hand-written complex markers in `requirements.txt`/`pyproject.toml`; refactoring a marker and dropping a `)`; line wrapping that lost the trailing paren.
Related errors
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/05bfc34a1e2f294e.json.
Report an issue: GitHub.