pypa/pip · error · SyntaxError
unterminated string: %s
Error message
unterminated string: %s
What it means
Raised by distlib's PEP 508 marker parser when a quoted string literal in a marker is opened but never closed before the end of input. The parser's inner loop exits via the `else` clause of the while loop (no closing quote found) and raises `SyntaxError: unterminated string`.
Source
Thrown at src/pip/_vendor/distlib/util.py:94
oq = '\'"'.replace(q, '')
remaining = remaining[1:]
parts = [q]
while remaining:
# either a string chunk, or oq, or q to terminate
if remaining[0] == q:
break
elif remaining[0] == oq:
parts.append(oq)
remaining = remaining[1:]
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]View on GitHub (pinned to d7d0d0a394)
Solutions
- Add the missing closing quote that matches the opening one (`'...'` or `"..."`).
- Avoid shell-splitting marker strings — quote the whole requirement when installing from a shell.
- Lint requirements files for balanced quotes.
Example fix
# before requests==2.28.0 ; python_version >= '3.8 # after requests==2.28.0 ; python_version >= '3.8'
Defensive patterns
Strategy: validation
Validate before calling
from packaging.markers import Marker, InvalidMarker
def validate_marker_closed(marker: str) -> None:
try:
Marker(marker)
except InvalidMarker as e:
raise ValueError(f"Marker parse error: {e}") from e Type guard
def marker_quotes_balanced(m: str) -> bool:
return m.count("'") % 2 == 0 and m.count('"') % 2 == 0 Try / catch
null
Prevention
- Always close string literals in markers with a matching quote.
- Quote the whole requirement in the shell to avoid quote stripping.
- Lint for balanced quotes in requirements files.
When it happens
Trigger: `parse_marker` encounters a marker like `python_version >= '3.8` (missing closing quote). Triggered when pip parses a requirement whose marker has an unbalanced quote.
Common situations: Truncated requirement lines; quotes stripped by shell expansion or by copy-paste; line continuations that broke the marker; requirements files edited with an editor that auto-paired quotes incorrectly.
Related errors
- error in string literal: %s
- unterminated parenthesis: %s
- name expected: %s
- invalid URI: %s
- Invalid URL: %s
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/27174602f0e8dcc6.json.
Report an issue: GitHub.