pypa/pip · error · ValueError
invalid glob %r: recursive glob "**" must be used alone
Error message
invalid glob %r: recursive glob "**" must be used alone
What it means
Raised as ValueError by distlib.util.iglob when the recursive glob '**' is not used alone — i.e. it is adjacent to characters other than '/', '\', ',', or '{'. The regex _CHECK_RECURSIVE_GLOB detects '**' glued directly to other text (like 'a**b' or '**foo'), which the extended globber cannot interpret. The contract is that '**' must be a standalone path component (e.g. 'src/**/tests').
Solutions
- Ensure '**' is surrounded by path separators: 'src/**/tests' not 'src/**tests'.
- Build patterns by joining components with '/' so '**' is always standalone.
- Pre-validate with the same _CHECK_RECURSIVE_GLOB regex before calling iglob.
Example fix
# before
iglob('logs/**files')
# after
iglob('logs/**/files') Defensive patterns
Strategy: validation
Validate before calling
from distlib.util import _CHECK_RECURSIVE_GLOB
def valid_glob(pattern):
return not _CHECK_RECURSIVE_GLOB.search(pattern) Type guard
def is_valid_recursive_glob(s: str) -> bool:
from distlib.util import _CHECK_RECURSIVE_GLOB
return isinstance(s, str) and not _CHECK_RECURSIVE_GLOB.search(s) Try / catch
try:
list(iglob(pattern))
except ValueError as e:
if 'recursive glob' in str(e):
pattern = pattern.replace('**', '**/') # normalize separators Prevention
- Always delimit '**' with '/' on both sides.
- Build globs by joining components.
When it happens
Trigger: iglob('src/**files'), iglob('foo**bar'), iglob('a/b**'), or any pattern where '**' touches non-separator characters. Correct usage like 'src/**/data' or '**/*.py' is fine because '**' is delimited by '/' or '{}'.
Common situations: Building glob patterns dynamically and accidentally concatenating '**' with a token; user-config globs with typos; porting glob patterns from libraries with looser '**' semantics.
Related errors
- invalid glob %r: mismatching set marker
- 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/d118eabdbe523889.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/distlib/util.py:1438
break
result /= 1000.0
return '%d %sB/s' % (result, unit)
#
# 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 itemView on GitHub (pinned to f399c37189)