{"id":"ce0c1beae3e81d5e","repo":"pypa/pip","slug":"req-path-recursively-references-itself-in-filen","errorCode":null,"errorMessage":"{req_path} recursively references itself in {filename}{tail}","messagePattern":"(.+?) recursively references itself in (.+?)(.+?)","errorType":"exception","errorClass":"RequirementsFileParseError","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/req/req_file.py","lineNumber":390,"sourceCode":"                # original file and nested file are paths\n                elif not SCHEME_RE.search(req_path):\n                    # do a join so relative paths work\n                    # and then abspath so that we can identify recursive references\n                    req_path = os.path.abspath(\n                        os.path.join(\n                            os.path.dirname(filename),\n                            req_path,\n                        )\n                    )\n                parsed_files = parsed_files_stack[0]\n                if req_path in parsed_files:\n                    initial_file = parsed_files[req_path]\n                    tail = (\n                        f\" and again in {initial_file}\"\n                        if initial_file is not None\n                        else \"\"\n                    )\n                    raise RequirementsFileParseError(\n                        f\"{req_path} recursively references itself in {filename}{tail}\"\n                    )\n                # Keeping a track where was each file first included in\n                new_parsed_files = parsed_files.copy()\n                new_parsed_files[req_path] = filename\n                yield from self._parse_and_recurse(\n                    req_path, nested_constraint, [new_parsed_files, *parsed_files_stack]\n                )\n            else:\n                yield line\n\n    def _parse_file(\n        self, filename: str, constraint: bool\n    ) -> Generator[ParsedLine, None, None]:\n        _, content = get_file_content(filename, self._session, constraint=constraint)\n\n        lines_enum = preprocess(content)\n","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/req/req_file.py#L372-L408","documentation":"RequirementsFileParseError raised when a requirements file referenced via '-r'/'--requirements' (or '-c' constraints) transitively includes itself, forming a cycle. pip tracks every file it has started parsing in parsed_files_stack and aborts as soon as a path reappears, to prevent infinite recursion. The message names the file and where it was first seen.","triggerScenarios":"a.txt contains '-r b.txt' and b.txt contains '-r a.txt'; or a.txt contains '-r a.txt' directly; or a self-referential symlink chain used as a requirements file. Triggered via 'pip install -r a.txt'.","commonSituations":"Refactoring a monorepo's shared requirements file and accidentally pointing a child back at the parent; symlink-based requirements files that loop; generated requirements files that emit their own filename.","solutions":["Inspect each file named in the message and remove the '-r'/'-c' line that points back up the chain.","If you need shared content, factor the common lines into a third file that both include, with nothing pointing back.","Check for symlinks: 'ls -l <file>' and resolve with 'readlink -f' to confirm no loop.","Re-run pip after editing to confirm the cycle is broken."],"exampleFix":"# before — requirements/a.txt\n-r requirements/b.txt\n# requirements/b.txt\n-r requirements/a.txt   # cycle\n\n# after — factor out shared\n# requirements/a.txt and requirements/b.txt both do:\n-r requirements/_common.txt","handlingStrategy":"validation","validationCode":"def detect_cycle(start: str) -> bool:\n    import os\n    seen, stack = set(), [os.path.abspath(start)]\n    while stack:\n        cur = stack.pop()\n        if cur in seen:\n            return True\n        seen.add(cur)\n        try:\n            for line in open(cur):\n                line = line.strip()\n                if line.startswith(('-r ', '--requirements ', '-c ', '--constraint ')):\n                    ref = line.split(maxsplit=1)[1]\n                    if not os.path.isabs(ref):\n                        ref = os.path.join(os.path.dirname(cur), ref)\n                    stack.append(os.path.abspath(ref))\n        except OSError:\n            pass\n    return False","typeGuard":"def is_acyclic_requirements_graph(root: str) -> bool:\n    return not detect_cycle(root)","tryCatchPattern":"if detect_cycle('requirements.txt'):\n    print('requirements cycle detected; edit files before pip')\nelse:\n    run_pip(['install', '-r', 'requirements.txt'])","preventionTips":["Keep requirements graphs as a DAG: factor shared deps into a common leaf file.","Lint '-r' references in pre-commit to catch back-edges.","Avoid symlinking requirements files to each other."],"tags":["requirements-file","recursion","config"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}