pypa/pip · error · RequirementsFileParseError
recursively references itself in
Error message
{req_path} recursively references itself in {filename}{tail} What it means
A requirements file (-r) or constraint file (-c) includes another file that, directly or transitively, includes the original file again, creating a cycle. pip tracks all files in the current parse stack (parsed_files_stack) and raises RequirementsFileParseError when it detects the same absolute path reappearing.
Solutions
- Trace the include chain from the error message (it names the files involved) and break the cycle
- Restructure requirements files into a non-circular hierarchy: a base file included by leaf files
- If you have shared constraints, put them in a single file that no other file includes back
- Use 'readlink -f' to detect symlink-induced cycles that create different paths to the same file
Example fix
# before — requirements.txt: -r other.txt # other.txt: -r requirements.txt # cycle! # after — requirements.txt: -r base.txt -r app.txt # base.txt and app.txt do NOT include requirements.txt
Defensive patterns
Strategy: validation
Validate before calling
import os
def find_circular_includes(filename: str, _seen: set | None = None) -> str | None:
"""Returns the path of a circular reference, or None."""
if _seen is None:
_seen = set()
abspath = os.path.abspath(filename)
if abspath in _seen:
return abspath
_seen.add(abspath)
try:
with open(abspath) as f:
for line in f:
line = line.strip()
if line.startswith("-r ") or line.startswith("-c "):
nested = line.split(None, 1)[1]
nested_path = os.path.join(os.path.dirname(abspath), nested)
result = find_circular_includes(nested_path, _seen.copy())
if result:
return result
except OSError:
pass
return None Type guard
import os
def has_no_circular_includes(filename: str) -> bool:
"""Type guard: True if the requirements file hierarchy is acyclic."""
return find_circular_includes(filename) is None Try / catch
from pip._internal.exceptions import RequirementsFileParseError
from pip._internal.req.req_file import parse_requirements
try:
list(parse_requirements("requirements.txt", session))
except RequirementsFileParseError as e:
if "recursively references itself" in str(e):
print("Break the circular -r/-c include chain")
else:
raise Prevention
- Design requirements files as a DAG: base files included by leaf files, never circular
- Use a validation script to detect circular includes before running pip
- Avoid symlinks in requirements file directories that could alias the same file
When it happens
Trigger: requirements.txt has '-r other.txt' and other.txt has '-r requirements.txt'. Or a single file contains '-r requirements.txt' referencing itself. The check 'if req_path in parsed_files' catches it.
Common situations: Refactoring requirements into shared files and accidentally creating circular includes. Symlinks that cause path aliasing. Generated requirements files that reference their generator.
Related errors
- Could not split options
- Invalid requirement
- Configuration file could not be loaded.\n
- Could not open file
- Error parsing
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/ce0c1beae3e81d5e.
Report an issue: GitHub.
Appendix: 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 f399c37189)