pypa/pip · error · InvalidEggFragment

invalid-egg-fragment

invalid-egg-fragment

Error message

The '{fragment}' egg fragment is invalid

What it means

Raised as InvalidEggFragment when a URL's '#egg=...' fragment does not parse as a valid PEP 508 project name. In link.py the captured egg fragment is matched against '^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$' (case-insensitive); any deviation (version specifiers, extras, leading digit-only, punctuation) is rejected. This guards legacy VCS/direct-URL installs that still rely on the egg fragment to identify the project.

Solutions

  1. Remove any version specifier or extras from the egg fragment so it is a bare project name: '#egg=pkg'.
  2. Prefer the modern direct-URL requirement syntax 'pkg @ git+https://...' which replaces the egg fragment entirely.
  3. For extras, use 'pkg[extra] @ git+https://...' rather than placing '[extra]' inside the egg fragment.
  4. Validate the fragment against the PEP 508 name regex before passing the URL to pip.

Example fix

# before
pip install "git+https://github.com/o/r.git#egg=pkg>=1.0"

# after
pip install "pkg @ git+https://github.com/o/r.git"
Defensive patterns

Strategy: validation

Validate before calling

import re
from urllib.parse import urlsplit

_PROJECT_NAME_RE = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I)

def valid_egg_fragment(url: str) -> bool:
    frag = urlsplit(url).fragment
    for pair in frag.split("&"):
        if pair.startswith("egg="):
            name = pair[len("egg="):]
            return bool(_PROJECT_NAME_RE.match(name))
    return True  # no egg fragment present

# assert valid_egg_fragment("git+https://o/r.git#egg=pkg")

Prevention

When it happens

Trigger: Calling pip install with a VCS or direct URL containing '#egg=' followed by a token that fails the PEP 508 project-name regex, e.g. '#egg=pkg>=1.0', '#egg=pkg[extra]', '#egg=1pkg', '#egg=pkg!='. Accessing Link.egg_fragment / the _egg_fragment() private method on such a Link triggers it.

Common situations: Copy-pasted install commands from outdated docs that stuff version specifiers or extras into the egg fragment; migrating old requirements.txt lines that used '#egg=name==1.0'; shell quoting that lets a comparison operator leak into the fragment.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/f0556b833ae091fe. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_internal/models/link.py:526

        return urllib.parse.urlunsplit((scheme, netloc, path, query, ""))

    _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")

    # Per PEP 508.
    _project_name_re = re.compile(
        r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
    )

    def _egg_fragment(self) -> str | None:
        match = self._egg_fragment_re.search(self._url)
        if not match:
            return None

        # An egg fragment looks like a PEP 508 project name, along with
        # an optional extras specifier. Anything else is invalid.
        project_name = match.group(1)
        if not self._project_name_re.match(project_name):
            raise InvalidEggFragment(self, project_name)

        return project_name

    _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")

    @property
    def subdirectory_fragment(self) -> str | None:
        match = self._subdirectory_fragment_re.search(self._url)
        if not match:
            return None
        return match.group(1)

    def metadata_link(self) -> Link | None:
        """Return a link to the associated core metadata file (if any)."""
        if self.metadata_file_data is None:
            return None
        metadata_url = f"{self.url_without_fragment}.metadata"
        if self.metadata_file_data.hashes is None:

View on GitHub (pinned to f399c37189)