pypa/pip · error · DirectUrlValidationError

Algorithm {hash_algorithm!r} used in hash field has differen

Error message

Algorithm {hash_algorithm!r} used in hash field has different value in hashes field

What it means

Raised by `ArchiveInfo._from_dict` when both `hashes` and legacy `hash` are present, the algorithm matches a key in `hashes`, but the digest value differs. This signals that the two fields disagree about the package's content hash — treated as a potential tampering/corruption indicator.

Source

Thrown at src/pip/_vendor/packaging/direct_url.py:212

            if "=" not in legacy_hash:
                raise DirectUrlValidationError(
                    "Invalid hash format (expected '<algorithm>=<hash>')",
                    context="hash",
                )
            hash_algorithm, hash_value = legacy_hash.split("=", 1)
            if hashes is None:
                # if `hashes` are not present, we can derive it from the legacy `hash`
                hashes = {hash_algorithm: hash_value}
            else:
                # if `hashes` are present, the legacy `hash` must match one of them
                if hash_algorithm not in hashes:
                    raise DirectUrlValidationError(
                        f"Algorithm {hash_algorithm!r} used in hash field "
                        f"is not present in hashes field",
                        context="hashes",
                    )
                if hashes[hash_algorithm] != hash_value:
                    raise DirectUrlValidationError(
                        f"Algorithm {hash_algorithm!r} used in hash field "
                        f"has different value in hashes field",
                        context="hash",
                    )
        return cls(hashes=hashes)


@dataclasses.dataclass(frozen=True, init=False)
class DirInfo:
    editable: bool | None = None

    def __init__(
        self,
        *,
        editable: bool | None = None,
    ) -> None:
        object.__setattr__(self, "editable", editable)

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Recompute the hash of the actual archive and update both fields consistently
  2. Remove the legacy `hash` field and keep only `hashes`
  3. Reinstall the package from a trusted index to regenerate clean metadata

Example fix

// before
{'archive_info': {'hashes': {'sha256':'aaa'}, 'hash': 'sha256=bbb'}}
// after
{'archive_info': {'hashes': {'sha256':'aaa'}, 'hash': 'sha256=aaa'}}
Defensive patterns

Strategy: validation

Validate before calling

def verify_hash_consistency(hashes: dict, legacy_hash: str) -> None:
    algo, value = legacy_hash.split('=', 1)
    if hashes is not None and hashes.get(algo) != value:
        raise ValueError(f'conflicting {algo} digest between hashes and legacy hash')

Type guard

def hash_values_match(hashes: dict, legacy_hash: str) -> bool:
    algo, value = legacy_hash.split('=', 1)
    return hashes is None or hashes.get(algo) == value

Try / catch

from packaging.direct_url import ArchiveInfo, DirectUrlValidationError
try:
    ArchiveInfo._from_dict(d)
except DirectUrlValidationError as e:
    if 'different value' in str(e):
        log.error('hash mismatch — possible corruption, reinstalling')
        d['archive_info'].pop('hash', None)
    raise

Prevention

When it happens

Trigger: A record like `hashes: {"sha256": "aaa"}` and legacy `hash: "sha256=bbb"` — same algorithm, conflicting digests.

Common situations: A wheel was re-published with new content but the legacy `hash` field was not updated; partial edits to install metadata; a corrupted or man-in-the-middle tampered record.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/a4d2794b5a795b26.json. Report an issue: GitHub.