pypa/pip · error · RequirementsFileParseError
{req_path} recursively references itself in {filename}{tail}
Error message
{req_path} recursively references itself in {filename}{tail} What it means
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.
Source
Thrown at src/pip/_internal/req/req_file.py:390
# original file and nested file are paths
elif not SCHEME_RE.search(req_path):
# do a join so relative paths work
# and then abspath so that we can identify recursive references
req_path = os.path.abspath(
os.path.join(
os.path.dirname(filename),
req_path,
)
)
parsed_files = parsed_files_stack[0]
if req_path in parsed_files:
initial_file = parsed_files[req_path]
tail = (
f" and again in {initial_file}"
if initial_file is not None
else ""
)
raise RequirementsFileParseError(
f"{req_path} recursively references itself in {filename}{tail}"
)
# Keeping a track where was each file first included in
new_parsed_files = parsed_files.copy()
new_parsed_files[req_path] = filename
yield from self._parse_and_recurse(
req_path, nested_constraint, [new_parsed_files, *parsed_files_stack]
)
else:
yield line
def _parse_file(
self, filename: str, constraint: bool
) -> Generator[ParsedLine, None, None]:
_, content = get_file_content(filename, self._session, constraint=constraint)
lines_enum = preprocess(content)
View on GitHub (pinned to d7d0d0a394)
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.
Example fix
# before — requirements/a.txt -r requirements/b.txt # requirements/b.txt -r requirements/a.txt # cycle # after — factor out shared # requirements/a.txt and requirements/b.txt both do: -r requirements/_common.txt
Defensive patterns
Strategy: validation
Validate before calling
def detect_cycle(start: str) -> bool:
import os
seen, stack = set(), [os.path.abspath(start)]
while stack:
cur = stack.pop()
if cur in seen:
return True
seen.add(cur)
try:
for line in open(cur):
line = line.strip()
if line.startswith(('-r ', '--requirements ', '-c ', '--constraint ')):
ref = line.split(maxsplit=1)[1]
if not os.path.isabs(ref):
ref = os.path.join(os.path.dirname(cur), ref)
stack.append(os.path.abspath(ref))
except OSError:
pass
return False Type guard
def is_acyclic_requirements_graph(root: str) -> bool:
return not detect_cycle(root) Try / catch
if detect_cycle('requirements.txt'):
print('requirements cycle detected; edit files before pip')
else:
run_pip(['install', '-r', 'requirements.txt']) Prevention
- 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.
When it happens
Trigger: 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'.
Common situations: 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.
Related errors
- Invalid requirement: {line}\n{e.msg}
- Could not split options: {options_str}
- Could not open {kind} file: {exc}
- Need exactly one file to operate upon (--user, --site, --glo
- Could not determine appropriate file.
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/ce0c1beae3e81d5e.json.
Report an issue: GitHub.