{"id":"3f3f7751e47b8e4d","repo":"pypa/pip","slug":"multiple-locked-links-provided-for-project-name","errorCode":null,"errorMessage":"Multiple locked links provided for {project_name}: {self._locked_links[project_name]} and {locked_link}","messagePattern":"Multiple locked links provided for (.+?): (.+?) and (.+?)","errorType":"exception","errorClass":"InstallationError","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/index/package_finder.py","lineNumber":1103,"sourceCode":"            logger.debug(\n                \"Using version %s (newest of versions: %s)\",\n                best_candidate.version,\n                _format_versions(best_candidate_result.applicable_candidates),\n            )\n            return best_candidate\n\n        # We have an existing version, and its the best version\n        logger.debug(\n            \"Installed version (%s) is most up-to-date (past versions: %s)\",\n            installed_version,\n            _format_versions(best_candidate_result.applicable_candidates),\n        )\n        raise BestVersionAlreadyInstalled\n\n    def add_locked_link(self, project_name: NormalizedName, locked_link: Link) -> None:\n        assert not self._all_candidates\n        if project_name in self._locked_links:\n            raise InstallationError(\n                f\"Multiple locked links provided for {project_name}: \"\n                f\"{self._locked_links[project_name]} and {locked_link}\"\n            )\n\n        self._locked_links[project_name] = locked_link\n\n\ndef _find_name_version_sep(fragment: str, canonical_name: str) -> int:\n    \"\"\"Find the separator's index based on the package's canonical name.\n\n    :param fragment: A <package>+<version> filename \"fragment\" (stem) or\n        egg fragment.\n    :param canonical_name: The package's canonical name.\n\n    This function is needed since the canonicalized name does not necessarily\n    have the same length as the egg info's name part. An example::\n\n    >>> fragment = 'foo__bar-1.0'","sourceCodeStart":1085,"sourceCodeEnd":1121,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/index/package_finder.py#L1085-L1121","documentation":"InstallationError raised in add_locked_link when a second locked link is registered for a project name that already has one. The locked-link table must map one project to exactly one artifact; a duplicate means the lockfile/constraints source is internally inconsistent.","triggerScenarios":"Triggered when code (lockfile loader / resolver) calls finder.add_locked_link(project_name, link) twice for the same canonical project_name with different Link objects. self._all_candidates is asserted empty, so it happens during the setup phase before any resolution.","commonSituations":"A lockfile or constraints file that pins two different files/URLs for the same package; a bug in a lockfile generator producing duplicate entries under different canonicalizations (e.g. 'Foo' and 'foo'); merging two lockfiles that both pin the same dependency differently.","solutions":["Inspect the lockfile/constraints file for duplicate entries for the named project (account for name normalization - 'Foo', 'foo', 'F-O-O' all canonicalize together).","Keep a single pinned artifact per project in the lock and remove the duplicate.","Regenerate the lockfile with a current lockfile tool to deduplicate automatically.","If merging locks, resolve the conflict explicitly before installing."],"exampleFix":"# before - locked-requirements.txt\nfoo==1.0 --hash=sha:aaa\nFoo==1.1 --hash=sha:bbb   # duplicate after canonicalization\n\n# after\nfoo==1.0 --hash=sha:aaa","handlingStrategy":"validation","validationCode":"from pip._internal.utils.misc import canonicalize_name\nseen = {}\nfor project, link in locked_links:\n    c = canonicalize_name(project)\n    if c in seen:\n        print(f'duplicate lock entry for {c}: {seen[c]} vs {link}')\n    seen[c] = link","typeGuard":"def no_duplicate_locks(locked_links) -> bool:\n    from pip._internal.utils.misc import canonicalize_name\n    names = [canonicalize_name(p) for p, _ in locked_links]\n    return len(names) == len(set(names))","tryCatchPattern":"from pip._internal.exceptions import InstallationError\ntry:\n    finder.add_locked_link(name, link)\nexcept InstallationError as e:\n    if 'Multiple locked links' in str(e):\n        # deduplicate the lockfile by canonical name\n        ...","preventionTips":["Canonicalize project names when generating lockfiles.","Deduplicate lock entries before installing.","Regenerate locks with a current tool to avoid normalization drift.","Treat 'Foo' and 'foo' as the same project."],"tags":["lockfile","constraints","duplicate","resolution"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}