pypa/pip · error · UnsupportedWheel
multiple .dist-info directories found: {}
Error message
multiple .dist-info directories found: {} What it means
UnsupportedWheel raised when the wheel zip contains more than one top-level `.dist-info` directory. The wheel spec mandates exactly one; multiple indicate a malformed build (often from concatenating or merging wheels, or a backend bug emitting metadata twice). The error lists all offending directory names.
Source
Thrown at src/pip/_internal/utils/wheel.py:51
return info_dir, metadata
def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
"""Returns the name of the contained .dist-info directory.
Raises AssertionError or UnsupportedWheel if not found, >1 found, or
it doesn't match the provided name.
"""
# 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)View on GitHub (pinned to d7d0d0a394)
Solutions
- Inspect: `unzip -l pkg.whl | grep dist-info` and identify the duplicates.
- Rebuild from a clean dist/ directory: `rm -rf dist build *.egg-info && python -m build --wheel`.
- If merging artifacts, keep only one .dist-info per wheel.
- Verify with `twine check` after rebuild.
Example fix
// before # stale dist/ from previous build concatenated python -m build --wheel // after rm -rf dist build *.egg-info && python -m build --wheel && twine check dist/*
Defensive patterns
Strategy: validation
Validate before calling
import zipfile
def exactly_one_dist_info(path: str) -> bool:
with zipfile.ZipFile(path) as z:
info_dirs = {n.split('/',1)[0] for n in z.namelist() if n.split('/',1)[0].endswith('.dist-info')}
return len(info_dirs) == 1 Type guard
null
Try / catch
null
Prevention
- Build from a clean dist/ each time: `rm -rf dist build *.egg-info`.
- Use a PEP 517 backend.
- Run `twine check` to catch duplicate metadata.
- Never merge wheels together.
When it happens
Trigger: wheel_dist_info_dir on a zip where two or more top-level entries end in .dist-info. Happens when zipping two wheels together, double-running the metadata generation step, or a backend that writes metadata for both an old and new build into one archive.
Common situations: Custom build script that appends to an existing wheel; incremental build leftover; tool that merges debug+release metadata.
Related errors
- {name} has an invalid wheel, {e}
- .dist-info directory not found
- .dist-info directory {info_dir!r} does not start with {canon
- error decoding {path!r}: {e!r}
- WHEEL is missing Wheel-Version
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/9c558818c12188a6.json.
Report an issue: GitHub.