pypa/pip · error · InstallationError
Could not install locked package
Error message
Could not install locked package {project_name!r} from {locked_link.comes_from!r}: {detail} What it means
Raised inside the locked-link code path of find_best_candidate (package_finder.py:900-905). When a lock file pins a specific link for a project, pip skips normal index resolution and evaluates only that locked link; if it fails the LinkEvaluator (wrong format, yanked, requires-python mismatch, release/format control rejection) pip raises InstallationError instead of falling back to the index, because a lock is an explicit, non-negotiable constraint.
Solutions
- Re-generate the lock file on the target platform/Python version so the pinned link passes evaluate_link there.
- Relax the conflicting --only-binary/--no-binary or release-control setting so the locked artifact's format is allowed.
- Remove or update the offending locked entry so pip re-resolves from the index.
Example fix
// before: lock pins source dist but install runs with --only-binary=:all: pip install --only-binary=:all: --require-hashes -r locked.txt // after: allow the sdist format the lock expects pip install --require-hashes -r locked.txt
Defensive patterns
Strategy: validation
Validate before calling
// Before applying a lock, pre-check the locked link against current format/release control:
from pip._internal.index.package_finder import LinkType
result, detail = link_evaluator.evaluate_link(locked_link)
if result != LinkType.candidate:
raise SystemExit(f'locked link for {project_name} will fail: {detail}') Try / catch
from pip._internal.exceptions import InstallationError
try:
best = finder.find_best_candidate(req)
except InstallationError as e:
if 'Could not install locked package' in str(e):
# fall back to full index resolution instead of the lock
... Prevention
- Generate lock files on the same platform/Python version where they will be applied.
- Keep --only-binary/--no-binary settings consistent between lock generation and install.
- Pre-validate locked links with the same LinkEvaluator before committing the lock.
When it happens
Trigger: Installing from a pip lock/resolution set where the pinned link is excluded by --only-binary/--no-binary format control, fails the requires_python check, is yanked, or is otherwise rejected by evaluate_link. Triggered via the locked-link resolution in find_best_candidate.
Common situations: A lock file generated on one platform being applied on another (different Python version -> requires_python mismatch); format-control options (--only-binary=:all:) that forbid the source/binary type of the locked artifact; a locked link that was later yanked or removed upstream.
Related errors
- Multiple locked links provided for
- No matching distribution found for
- Cannot determine archive format of
- Cannot select requirements from pylock file
- Cannot set --home and --prefix together
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/a1fefd2fb0d9c22d.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/index/package_finder.py:902
All versions found are returned as an InstallationCandidate list.
See LinkEvaluator.evaluate_link() for details on which files
are accepted.
"""
if project_name in self._all_candidates:
return self._all_candidates[project_name]
link_evaluator = self.make_link_evaluator(project_name)
if locked_link := self._locked_links.get(canonicalize_name(project_name)):
# If a locked link is known for that project, do not check
# index_urls nor find_links. We don't use get_install_candidate here,
# because if a locked link is unsupported (due to format control,
# release control or otherwise), we want to error out immediately
# instead of ignoring it.
result, detail = link_evaluator.evaluate_link(locked_link)
if result != LinkType.candidate:
raise InstallationError(
f"Could not install locked package {project_name!r} "
f"from {locked_link.comes_from!r}: {detail}"
)
self._all_candidates[project_name] = [
InstallationCandidate(project_name, detail, locked_link, locked=True)
]
return self._all_candidates[project_name]
collected_sources = self._link_collector.collect_sources(
project_name=project_name,
candidates_from_page=functools.partial(
self.process_project_url,
link_evaluator=link_evaluator,
),
)
page_candidates_it = itertools.chain.from_iterable(
source.page_candidates()View on GitHub (pinned to f399c37189)