pypa/pip · error · HashUnpinned

In --require-hashes mode, all requirements must have their v

Error message

In --require-hashes mode, all requirements must have their versions pinned with ==. These do not:

What it means

Raised as HashUnpinned when --require-hashes is active and a (non-direct) requirement is not pinned with an exact '==' specifier. At prepare.py:483-484, _get_linked_req_hashes checks 'not req.is_direct and not req.is_pinned' and aborts, because a floating version would cause a future hash mismatch when a new version is uploaded. This is a reproducibility guard, not strictly a security check.

Source

Thrown at src/pip/_internal/operations/prepare.py:484

        if not self.require_hashes:
            return req.hashes(trust_internet=True)

        # We could check these first 2 conditions inside unpack_url
        # and save repetition of conditions, but then we would
        # report less-useful error messages for unhashable
        # requirements, complaining that there's no hash provided.
        if req.link.is_vcs:
            raise VcsHashUnsupported()
        if req.link.is_existing_dir():
            raise DirectoryUrlHashUnsupported()

        # Unpinned packages are asking for trouble when a new version
        # is uploaded.  This isn't a security check, but it saves users
        # a surprising hash mismatch in the future.
        # file:/// URLs aren't pinnable, so don't complain about them
        # not being pinned.
        if not req.is_direct and not req.is_pinned:
            raise HashUnpinned()

        # If known-good hashes are missing for this requirement,
        # shim it with a facade object that will provoke hash
        # computation and then raise a HashMissing exception
        # showing the user what the hash should be.
        return req.hashes(trust_internet=False) or MissingHashes()

    def _fetch_metadata_only(
        self,
        req: InstallRequirement,
    ) -> BaseDistribution | None:
        if self.legacy_resolver:
            logger.debug(
                "Metadata-only fetching is not used in the legacy resolver",
            )
            return None
        if self.require_hashes:
            logger.debug(

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Pin every requirement to an exact version with '==' in the requirements file.
  2. Regenerate hashes for the pinned set: pip install --require-hashes -r requirements.txt (pip will report the expected hashes), or use pip-compile / pip freeze to produce pinned hashes.
  3. Ensure no requirement uses '>=', '~=', '<', '*', or bare names when --require-hashes is on.
  4. For transitive deps, pin them explicitly in the (hashed) requirements file.

Example fix

# before (requirements.txt)
--require-hashes
requests >=2.0

# after
--require-hashes
requests==2.31.0 \
  --hash=sha256:aaaa... \
  --hash=sha256:bbbb...
Defensive patterns

Strategy: validation

Validate before calling

from pip._vendor.packaging.requirements import Requirement

def assert_all_pinned(requirements_text):
    unpinned = []
    for line in requirements_text.splitlines():
        line = line.split("#", 1)[0].strip()
        if not line or line.startswith("-"):
            continue
        try:
            req = Requirement(line)
        except Exception:
            continue
        specs = str(req.specifier)
        if "==" not in specs or "*" in specs or "~=" in specs or ">" in specs or "<" in specs.replace("==",""):
            unpinned.append(line)
    if unpinned:
        raise SystemExit(f"unpinned under --require-hashes: {unpinned}")

Type guard

from pip._vendor.packaging.requirements import Requirement
def is_exact_pinned(line: str) -> bool:
    try:
        req = Requirement(line)
    except Exception:
        return False
    return any(op == "==" for op, _ in req.specifier)

Prevention

When it happens

Trigger: A requirements file used with --require-hashes contains a requirement like 'requests>=2.0' or 'requests' (no specifier, or a range/compatible-release) instead of 'requests==2.31.0'.

Common situations: Generating a hashed requirements.txt via pip freeze without pinning, or mixing a constraints file that leaves versions floating. Common when adopting --require-hashes on an existing unpinned requirements file.

Related errors


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