pypa/pip · error · VcsHashUnsupported

Can't verify hashes for these requirements because we don't…

Error message

Can't verify hashes for these requirements because we don't have a way to hash version control repositories:

What it means

Raised by _get_linked_req_hashes (prepare.py:473) as VcsHashUnsupported when pip is in --require-hashes mode and the requirement resolves to a VCS URL (git+/hg+/svn+/bzr+). pip cannot compute a stable cryptographic hash over a version-control checkout, so hash-verification mode is incompatible with VCS requirements.

Solutions

  1. Remove the VCS requirement from the hashed installation set, or install it separately without --require-hashes.
  2. Vendor the VCS project into a local wheel/sdist and pin a hash for that artifact instead.
  3. If only a fixed commit is needed, build a wheel from the checkout and reference the wheel with its hash.
  4. Do not use --require-hashes for environments that legitimately need editable/VCS installs.

Example fix

# before
pip install --require-hashes -r locked.txt   # locked.txt contains: git+https://example/repo.git
# after: build & hash an artifact instead
pip wheel --no-deps git+https://example/repo.git@<commit> -w ./wheels
pip install --require-hashes ./wheels/Repo-1.0-py3-none-any.whl --hash sha256:...
Defensive patterns

Strategy: validation

Validate before calling

# Detect VCS requirements before running pip with --require-hashes so they can be excluded/converted.
from pip._internal.vcs import vcs

def is_vcs_requirement(line: str) -> bool:
    low = line.lower()
    return any(low.startswith(s + ':') or low.startswith(s + '+') for s in vcs.all_schemes)

def no_vcs_in_hashed_file(path: str) -> bool:
    with open(path) as f:
        return not any(is_vcs_requirement(l.split('#')[0].strip()) for l in f if l.strip())

Type guard

from pip._internal.vcs import vcs

def is_vcs_url(url: str) -> bool:
    low = url.lower()
    return any(low.startswith(s + ':') or low.startswith(s + '+') for s in vcs.all_schemes)

Try / catch

from subprocess import run, CalledProcessError
try:
    run(["pip", "install", "--require-hashes", "-r", req_file], check=True)
except CalledProcessError:
    # Fallback: build VCS deps into hashed artifacts first.
    run(["pip", "wheel", "--no-deps", vcs_url, "-w", "./wheels"], check=True)

Prevention

When it happens

Trigger: Running with --require-hashes (or a hashed requirements file) while installing a VCS requirement like 'git+https://.../repo.git'. Detected via req.link.is_vcs before hashes are checked.

Common situations: A locked/hashed requirements file that accidentally includes a -e git+... editable or a VCS direct reference; mixing a security-pinned environment with a development VCS dependency.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/c891aa1a416ec578. Report an issue: GitHub.

Appendix: source

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

            parallel_builds=parallel_builds,
        )
        req.ensure_pristine_source_checkout()

    def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
        # By the time this is called, the requirement's link should have
        # been checked so we can tell what kind of requirements req is
        # and raise some more informative errors than otherwise.
        # (For example, we can raise VcsHashUnsupported for a VCS URL
        # rather than HashMissing.)
        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(

View on GitHub (pinned to f399c37189)