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

  1. Inspect: `unzip -l pkg.whl | grep dist-info` and identify the duplicates.
  2. Rebuild from a clean dist/ directory: `rm -rf dist build *.egg-info && python -m build --wheel`.
  3. If merging artifacts, keep only one .dist-info per wheel.
  4. 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

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


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/9c558818c12188a6.json. Report an issue: GitHub.