pypa/pip · error · UnsupportedWheel
.dist-info directory
Error message
.dist-info directory {info_dir!r} does not start with {canonical_name!r} What it means
Raised by wheel_dist_info_dir() when the single .dist-info directory name, after canonicalization (PEP 503 normalization), does not start with the canonicalized wheel name. This check ensures the metadata directory corresponds to the distribution being installed, catching mismatches between the wheel filename and its internal metadata.
Solutions
- Compare the wheel filename's project name with the .dist-info directory name inside it
- Rebuild the wheel ensuring the project name in pyproject.toml/setup.py matches the filename
- Re-download the wheel from the official index to ensure filename and contents are consistent
- Do not manually rename wheel files; rebuild them with the correct name
Example fix
// before # wheel renamed from old-name-1.0.whl to new-name-1.0.whl pip install ./new-name-1.0-py3-none-any.whl // after # set the correct project name in pyproject.toml then rebuild [project] name = "new-name" python -m build --wheel pip install dist/new-name-1.0-py3-none-any.whl
Defensive patterns
Strategy: validation
Validate before calling
import zipfile
from pip._vendor.packaging.utils import canonicalize_name
import os
def wheel_name_matches_dist_info(path: str) -> bool:
# extract project name from filename: name-version-pythontag-abitag-plattag.whl
wheel_name = os.path.basename(path).split('-', 1)[0]
canonical_wheel = canonicalize_name(wheel_name)
with zipfile.ZipFile(path) as zf:
dirs = [n.split('/')[0] for n in zf.namelist() if '/' in n]
info_dirs = [d for d in set(dirs) if d.endswith('.dist-info')]
if len(info_dirs) != 1:
return False
return canonicalize_name(info_dirs[0]).startswith(canonical_wheel) Type guard
from pip._vendor.packaging.utils import canonicalize_name
def dist_info_matches_name(info_dir: str, wheel_name: str) -> bool:
return canonicalize_name(info_dir).startswith(canonicalize_name(wheel_name)) Try / catch
from pip._internal.exceptions import UnsupportedWheel
try:
wheel_dist_info_dir(wheel_zip, name)
except UnsupportedWheel as e:
if 'does not start with' in str(e):
raise PackagingError(f'wheel filename/metadata mismatch: {e}')
raise Prevention
- Never manually rename wheel files
- Ensure pyproject.toml/setup.py project name matches the output wheel name
- Rebuild wheels with a standard backend after changing the project name
- Verify filename-to-metadata consistency before publishing
When it happens
Trigger: Calling wheel_dist_info_dir(source, name) where canonicalize_name(info_dir) does not start with canonicalize_name(name). For example, a wheel named 'foo-bar-1.0.whl' whose .dist-info is 'baz-1.0.dist-info'.
Common situations: A wheel manually renamed to a different distribution name without rebuilding. A build backend configured with the wrong project name. A vendored or repackaged wheel whose filename was changed but contents were not. A typo-squatting package mimicking another name.
Related errors
- .dist-info directory not found
- invalid Wheel-Version
- multiple .dist-info directories found
- has an invalid wheel
- WHEEL is missing Wheel-Version
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/577d57f8b3a44f25.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/utils/wheel.py:60
# Zip file path separators must be /
subdirs = {p.split("/", 1)[0] for p in source.namelist()}
info_dirs = [s for s in subdirs if s.endswith(".dist-info")]
if not info_dirs:
raise UnsupportedWheel(".dist-info directory not found")
if len(info_dirs) > 1:
raise UnsupportedWheel(
"multiple .dist-info directories found: {}".format(", ".join(info_dirs))
)
info_dir = info_dirs[0]
info_dir_name = canonicalize_name(info_dir)
canonical_name = canonicalize_name(name)
if not info_dir_name.startswith(canonical_name):
raise UnsupportedWheel(
f".dist-info directory {info_dir!r} does not start with {canonical_name!r}"
)
return info_dir
def read_wheel_metadata_file(source: ZipFile, path: str) -> bytes:
try:
return source.read(path)
# BadZipFile for general corruption, KeyError for missing entry,
# and RuntimeError for password-protected files
except (BadZipFile, KeyError, RuntimeError) as e:
raise UnsupportedWheel(f"could not read {path!r} file: {e!r}")
def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
"""Return the WHEEL metadata of an extracted wheel, if possible.
Otherwise, raise UnsupportedWheel.View on GitHub (pinned to f399c37189)