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

  1. Trace the include chain from the error message (it names the files involved) and break the cycle
  2. Restructure requirements files into a non-circular hierarchy: a base file included by leaf files
  3. If you have shared constraints, put them in a single file that no other file includes back
  4. 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

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


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)