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

  1. Re-generate the lock file on the target platform/Python version so the pinned link passes evaluate_link there.
  2. Relax the conflicting --only-binary/--no-binary or release-control setting so the locked artifact's format is allowed.
  3. 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

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


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)