pypa/pip · error · LegacyDistutilsInstall
uninstall-distutils-installed-package
uninstall-distutils-installed-package
Error message
Cannot uninstall {distribution} What it means
In the uninstall path (req_uninstall.py:510), when a distribution was installed by plain distutils (dist.installed_by_distutils), pip raises LegacyDistutilsInstall (code uninstall-distutils-installed-package). distutils installs don't write a usable RECORD, so pip cannot accurately determine the file set and refuses a partial uninstall.
Solutions
- Reinstall the package properly via pip (pip install --force-reinstall <pkg>) so it gets a RECORD, then uninstall.
- Manually remove the installed files and .egg-info directory.
- Start fresh in a new virtualenv to avoid the legacy install entirely.
Example fix
# before python setup.py install # later: pip uninstall fails # after pip install . # then pip uninstall works
Defensive patterns
Strategy: validation
Validate before calling
# Detect distutils-installed packages before uninstall.
import pathlib, sys, importlib.metadata as md
for dist in md.distributions():
base = pathlib.Path(dist.locate_file(""))
egg_info = list(base.parent.glob("*.egg-info"))
installer = (pathlib.Path(dist._path).parent / "INSTALLER").read_text().strip() if hasattr(dist, '_path') else ""
if egg_info and installer not in ("pip",):
print(f"Possibly distutils-installed: {dist.metadata['Name']}") Prevention
- Never use `python setup.py install`; always use `pip install`.
- Start new projects on a fresh venv with pip-only installs.
- If you inherit an env, reinstall legacy packages via pip before uninstalling.
When it happens
Trigger: Uninstalling a package that was installed via `python setup.py install` using the stdlib distutils (no wheel, no RECORD).
Common situations: Legacy `python setup.py install` workflows; old system-Python packages; packages from before pip/wheel became standard.
Related errors
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/1a0adb70872ecf69.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/req/req_uninstall.py:510
try:
namespace_packages = dist.read_text("namespace_packages.txt")
except FileNotFoundError:
namespaces = []
else:
namespaces = namespace_packages.splitlines(keepends=False)
for top_level_pkg in [
p
for p in dist.read_text("top_level.txt").splitlines()
if p and p not in namespaces
]:
path = os.path.join(dist_location, top_level_pkg)
paths_to_remove.add(path)
paths_to_remove.add(f"{path}.py")
paths_to_remove.add(f"{path}.pyc")
paths_to_remove.add(f"{path}.pyo")
elif dist.installed_by_distutils:
raise LegacyDistutilsInstall(distribution=dist)
elif dist.installed_as_egg:
# package installed by easy_install
# We cannot match on dist.egg_name because it can slightly vary
# i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
# XXX We use normalized_dist_location because dist_location my contain
# a trailing / if the distribution is a zipped egg
# (which is not a directory).
paths_to_remove.add(normalized_dist_location)
easy_install_egg = os.path.split(normalized_dist_location)[1]
easy_install_pth = os.path.join(
os.path.dirname(normalized_dist_location),
"easy-install.pth",
)
paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)
elif dist.installed_with_dist_info:
for path in uninstallation_paths(dist):View on GitHub (pinned to f399c37189)