{"id":"7cbfa0df22038088","repo":"pypa/pip","slug":"algorithm-hash-algorithm-r-used-in-hash-field-is","errorCode":null,"errorMessage":"Algorithm {hash_algorithm!r} used in hash field is not present in hashes field","messagePattern":"Algorithm (.+?) used in hash field is not present in hashes field","errorType":"validation","errorClass":"DirectUrlValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/direct_url.py","lineNumber":206,"sourceCode":"        if hashes is not None and not all(isinstance(h, str) for h in hashes.values()):\n            raise DirectUrlValidationError(\n                \"Hash values must be strings\", context=\"hashes\"\n            )\n        legacy_hash = _get(d, str, \"hash\")\n        if legacy_hash is not None:\n            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    editable: bool | None = None\n\n    def __init__(","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/direct_url.py#L188-L224","documentation":"Raised by `ArchiveInfo._from_dict` when both the modern `hashes` mapping and the legacy `hash` string are present, but the algorithm named in the legacy `hash` (e.g. `md5=...`) does not appear as a key in `hashes`. The legacy field must be consistent with `hashes` — pip uses this cross-check to detect tampering or schema drift.","triggerScenarios":"A `direct_url.json` with `hashes: {\"sha256\": \"aaa\"}` and legacy `hash: \"md5=bbb\"` — the `md5` algorithm is referenced in `hash` but absent from `hashes`.","commonSituations":"A record partially rewritten by a tool that updated `hashes` but left a stale legacy `hash`; manual edits that added an extra algorithm; inconsistent metadata after a wheel was re-hashed.","solutions":["Make the legacy `hash` reference an algorithm that exists in `hashes`","Drop the redundant legacy `hash` field entirely (modern `hashes` is sufficient)","Reinstall the package to regenerate a consistent record"],"exampleFix":"// before\n{'archive_info': {'hashes': {'sha256':'aaa'}, 'hash': 'md5=bbb'}}\n// after\n{'archive_info': {'hashes': {'sha256':'aaa'}, 'hash': 'sha256=aaa'}}","handlingStrategy":"validation","validationCode":"def reconcile_hashes(hashes: dict, legacy_hash: str) -> None:\n    algo, _ = legacy_hash.split('=', 1)\n    if hashes is not None and algo not in hashes:\n        raise ValueError(f'legacy hash algorithm {algo!r} missing from hashes')","typeGuard":"def hashes_are_consistent(hashes: dict, legacy_hash: str) -> bool:\n    algo, _ = legacy_hash.split('=', 1)\n    return algo in hashes","tryCatchPattern":"from packaging.direct_url import ArchiveInfo, DirectUrlValidationError\ntry:\n    ArchiveInfo._from_dict(d)\nexcept DirectUrlValidationError as e:\n    if 'not present in hashes' in str(e):\n        d['archive_info'].pop('hash', None)  # drop inconsistent legacy field\n    raise","preventionTips":["Use either hashes or legacy hash, not both, when generating records","Keep hash fields in sync when mutating install metadata","Reinstall to regenerate consistent records"],"tags":["packaging","direct-url","pep610","hashes","consistency"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}