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
- Remove any version specifier or extras from the egg fragment so it is a bare project name: '#egg=pkg'.
- Prefer the modern direct-URL requirement syntax 'pkg @ git+https://...' which replaces the egg fragment entirely.
- For extras, use 'pkg[extra] @ git+https://...' rather than placing '[extra]' inside the egg fragment.
- 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
- Prefer the direct-URL requirement syntax 'name @ url' over egg fragments.
- Never place version specifiers or extras inside the #egg= fragment.
- Validate fragments with the PEP 508 name regex before passing URLs to pip.
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
- Cannot serialize marker value containing both quote…
- Expected end of dependency specifier
- Expected end of marker expression
- Expected extra name after comma
- Expected URL after @
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)