{"record":{"id":"a4d2794b5a795b26","repo":"pypa/pip","slug":"algorithm-hash-algorithm-r-used-in-hash-field-ha","errorCode":null,"errorMessage":"Algorithm {hash_algorithm!r} used in hash field has different value in hashes field","messagePattern":"Algorithm (.+?) used in hash field has different value in hashes field","errorType":"validation","errorClass":"DirectUrlValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/direct_url.py","lineNumber":224,"sourceCode":"            if \"=\" not in legacy_hash:\n                raise DirectUrlValidationError(\n                    \"Invalid hash format (expected '<algorithm>=<hash>')\",\n                    context=\"hash\",\n                )\n            hash_algorithm, hash_value = legacy_hash.split(\"=\", 1)\n            if hashes is None:\n                # if `hashes` are not present, we can derive it from the legacy `hash`\n                hashes = {hash_algorithm: hash_value}\n            else:\n                # if `hashes` are present, the legacy `hash` must match one of them\n                if hash_algorithm not in hashes:\n                    raise DirectUrlValidationError(\n                        f\"Algorithm {hash_algorithm!r} used in hash field \"\n                        f\"is not present in hashes field\",\n                        context=\"hashes\",\n                    )\n                if hashes[hash_algorithm] != hash_value:\n                    raise DirectUrlValidationError(\n                        f\"Algorithm {hash_algorithm!r} used in hash field \"\n                        f\"has different value in hashes field\",\n                        context=\"hash\",\n                    )\n        return cls(hashes=hashes)\n\n\n@dataclasses.dataclass(frozen=True, init=False)\nclass DirInfo:\n    \"\"\"The local directory information of a :class:`DirectUrl`.\"\"\"\n\n    editable: bool | None = None\n\n    def __init__(\n        self,\n        *,\n        editable: bool | None = None,\n    ) -> None:","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/direct_url.py#L206-L242","documentation":"When both the legacy hash field and the hashes mapping exist and the algorithm from hash is present in hashes, the digest values must match exactly. If they differ, DirectUrlValidationError is raised, indicating a hash conflict that could signal tampering or corruption.","triggerScenarios":"An archive_info block where the same algorithm appears in both hash and hashes with different digest values: e.g. {'hash': 'sha256=aaa', 'hashes': {'sha256': 'bbb'}}.","commonSituations":"Metadata edited by hand or by buggy tools. Hash recomputation that updated one field but not the other. Potential supply-chain integrity concern if the conflict is unexpected.","solutions":["Recompute the correct hash from the actual file and update both fields (or just hashes)","Remove the legacy hash field to eliminate the conflict","Investigate if the conflict is unexpected as it may indicate file corruption or tampering"],"exampleFix":"# before\ndata = {\n    \"url\": \"https://example.com/pkg.tar.gz\",\n    \"archive_info\": {\n        \"hash\": \"sha256=old_value\",\n        \"hashes\": {\"sha256\": \"new_value\"}  # mismatched\n    }\n}\n\n# after\nimport hashlib\ncorrect = hashlib.sha256(file_bytes).hexdigest()\ndata = {\n    \"url\": \"https://example.com/pkg.tar.gz\",\n    \"archive_info\": {\"hashes\": {\"sha256\": correct}}\n}","handlingStrategy":"validation","validationCode":"def validate_hash_value_match(archive_info: dict) -> None:\n    legacy = archive_info.get(\"hash\")\n    hashes = archive_info.get(\"hashes\")\n    if legacy and hashes:\n        algo, digest = legacy.split(\"=\", 1)\n        if algo in hashes and hashes[algo] != digest:\n            raise ValueError(f\"Hash mismatch for {algo!r}: legacy={digest!r} vs hashes={hashes[algo]!r}\")","typeGuard":"def do_hash_values_match(archive_info: dict) -> bool:\n    legacy = archive_info.get(\"hash\")\n    hashes = archive_info.get(\"hashes\")\n    if not legacy or not hashes:\n        return True\n    algo, digest = legacy.split(\"=\", 1)\n    return algo not in hashes or hashes[algo] == digest","tryCatchPattern":"from packaging.direct_url import DirectUrl, DirectUrlValidationError\n\ntry:\n    du = DirectUrl.from_dict(data)\nexcept DirectUrlValidationError as e:\n    if \"different value\" in str(e):\n        data[\"archive_info\"].pop(\"hash\")\n        du = DirectUrl.from_dict(data)","preventionTips":["Never manually edit hash fields","Regenerate both hash representations from the same computation","Drop legacy hash fields when migrating to hashes"],"tags":["packaging","pep610","json-validation","hashes","integrity","vendored"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}