{"id":"d118eabdbe523889","repo":"pypa/pip","slug":"invalid-glob-r-recursive-glob-must-be-used","errorCode":null,"errorMessage":"invalid glob %r: recursive glob \"**\" must be used alone","messagePattern":"invalid glob %r: recursive glob \"\\*\\*\" must be used alone","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":1438,"sourceCode":"                break\n            result /= 1000.0\n        return '%d %sB/s' % (result, unit)\n\n\n#\n# Glob functionality\n#\n\nRICH_GLOB = re.compile(r'\\{([^}]*)\\}')\n_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\\\,{]\\*\\*|\\*\\*[^/\\\\,}]')\n_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\\}|\\{[^}]*$')\n\n\ndef iglob(path_glob):\n    \"\"\"Extended globbing function that supports ** and {opt1,opt2,opt3}.\"\"\"\n    if _CHECK_RECURSIVE_GLOB.search(path_glob):\n        msg = \"\"\"invalid glob %r: recursive glob \"**\" must be used alone\"\"\"\n        raise ValueError(msg % path_glob)\n    if _CHECK_MISMATCH_SET.search(path_glob):\n        msg = \"\"\"invalid glob %r: mismatching set marker '{' or '}'\"\"\"\n        raise ValueError(msg % path_glob)\n    return _iglob(path_glob)\n\n\ndef _iglob(path_glob):\n    rich_path_glob = RICH_GLOB.split(path_glob, 1)\n    if len(rich_path_glob) > 1:\n        assert len(rich_path_glob) == 3, rich_path_glob\n        prefix, set, suffix = rich_path_glob\n        for item in set.split(','):\n            for path in _iglob(''.join((prefix, item, suffix))):\n                yield path\n    else:\n        if '**' not in path_glob:\n            for item in std_iglob(path_glob):\n                yield item","sourceCodeStart":1420,"sourceCodeEnd":1456,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L1420-L1456","documentation":"Raised by iglob() when a glob pattern uses the recursive '**' token adjacent to other characters (matched by _CHECK_RECURSIVE_GLOB). distlib only supports '**' as a standalone path segment (e.g. 'src/**' or '**/test_*.py'); using it like 'a**b' or 'foo**bar' is rejected with ValueError. '**' must be used alone within its path component.","triggerScenarios":"iglob('src/***.py'), iglob('foo**bar'), iglob('a/**b'), or any pattern where '**' is directly concatenated with non-separator characters.","commonSituations":"Patterns imported from shells that allow looser '**', or programmatically-built globs that embed '**' inside a larger token.","solutions":["Isolate '**' as its own segment: 'src/**' or '**/tests/*.py'.","Use '*' or a brace set '{a,b}' instead of '**' for fixed-width matching.","Re-read the pattern at the reported location and split '**' onto its own path component."],"exampleFix":"// before\nlist(iglob('src/***.py'))\n// after\nlist(iglob('src/**/*.py'))","handlingStrategy":"validation","validationCode":"import re\n_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\\\,{]\\*\\*|\\*\\*[^/\\\\,}]')\ndef safe_iglob(pattern):\n    if _CHECK_RECURSIVE_GLOB.search(pattern):\n        raise ValueError('recursive ** must be a standalone segment: %r' % pattern)\n    from distlib.util import iglob\n    return list(iglob(pattern))","typeGuard":null,"tryCatchPattern":"from distlib.util import iglob\ntry:\n    matches = list(iglob(pattern))\nexcept ValueError as e:\n    if 'recursive glob' in str(e):\n        pattern = pattern.replace('**', '/*/')  # fall back to single-level\n        matches = list(iglob(pattern))\n    else:\n        raise","preventionTips":["Always place '**' as its own path segment between slashes or at an end.","Validate glob patterns with _CHECK_RECURSIVE_GLOB before calling iglob().","Avoid concatenating '**' with other characters."],"tags":["glob","filesystem","validation","packaging"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}