pypa/pip · error · ValueError
invalid glob %r: mismatching set marker
Error message
invalid glob %r: mismatching set marker '{' or '}' What it means
Raised as ValueError by distlib.util.iglob when the pattern has mismatched set markers '{' or '}'. The regex _CHECK_MISMATCH_SET matches a '}' with no preceding '{' in the leading portion, or a '{' with no following '}' to the end — i.e. unbalanced brace-set syntax. The extended globber uses '{a,b,c}' for alternation sets; an orphan brace cannot be parsed.
Solutions
- Ensure every '{' has a matching '}' and they are correctly ordered.
- Validate brace balance before calling: pattern.count('{') == pattern.count('}').
- Build alternation sets from a list and format with '{%s}' % ','.join(options).
Example fix
# before
iglob('tests/{unit,integration')
# after
iglob('tests/{unit,integration}') Defensive patterns
Strategy: validation
Validate before calling
def valid_glob(pattern):
return pattern.count('{') == pattern.count('}') and '}' not in pattern.split('{')[0] Type guard
def has_balanced_braces(s: str) -> bool:
return isinstance(s, str) and s.count('{') == s.count('}') Try / catch
try:
list(iglob(pattern))
except ValueError as e:
if 'mismatching set marker' in str(e):
pattern += '}' if pattern.count('{') > pattern.count('}') else '' Prevention
- Ensure every '{' has a matching '}'.
- Build alternation sets from lists, not string concat.
When it happens
Trigger: iglob('src/{python,ruby'), iglob('src/python}'), or any pattern where '{' and '}' counts are unbalanced or appear out of order. Valid 'src/{a,b}/c' is accepted.
Common situations: Dynamically building alternation sets and dropping a closing brace; config-supplied glob patterns with typos; concatenating partial patterns that break brace nesting.
Related errors
- invalid glob %r: recursive glob "**" must be used alone
- file '%r' does not exist
- path ' ' cannot be absolute
- path ' ' cannot end with '/
- which would be overwritten
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/fb6116844a3110b6.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/distlib/util.py:1441
#
# Glob functionality
#
RICH_GLOB = re.compile(r'\{([^}]*)\}')
_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
def iglob(path_glob):
"""Extended globbing function that supports ** and {opt1,opt2,opt3}."""
if _CHECK_RECURSIVE_GLOB.search(path_glob):
msg = """invalid glob %r: recursive glob "**" must be used alone"""
raise ValueError(msg % path_glob)
if _CHECK_MISMATCH_SET.search(path_glob):
msg = """invalid glob %r: mismatching set marker '{' or '}'"""
raise ValueError(msg % path_glob)
return _iglob(path_glob)
def _iglob(path_glob):
rich_path_glob = RICH_GLOB.split(path_glob, 1)
if len(rich_path_glob) > 1:
assert len(rich_path_glob) == 3, rich_path_glob
prefix, set, suffix = rich_path_glob
for item in set.split(','):
for path in _iglob(''.join((prefix, item, suffix))):
yield path
else:
if '**' not in path_glob:
for item in std_iglob(path_glob):
yield item
else:
prefix, radical = path_glob.split('**', 1)
if prefix == '':View on GitHub (pinned to f399c37189)