{"id":"d93995f322538bd7","repo":"pypa/pip","slug":"in-require-hashes-mode-all-requirements-must-ha","errorCode":null,"errorMessage":"In --require-hashes mode, all requirements must have their versions pinned with ==. These do not:","messagePattern":"In --require-hashes mode, all requirements must have their versions pinned with ==\\. These do not:","errorType":"exception","errorClass":"HashUnpinned","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/operations/prepare.py","lineNumber":484,"sourceCode":"        if not self.require_hashes:\n            return req.hashes(trust_internet=True)\n\n        # We could check these first 2 conditions inside unpack_url\n        # and save repetition of conditions, but then we would\n        # report less-useful error messages for unhashable\n        # requirements, complaining that there's no hash provided.\n        if req.link.is_vcs:\n            raise VcsHashUnsupported()\n        if req.link.is_existing_dir():\n            raise DirectoryUrlHashUnsupported()\n\n        # Unpinned packages are asking for trouble when a new version\n        # is uploaded.  This isn't a security check, but it saves users\n        # a surprising hash mismatch in the future.\n        # file:/// URLs aren't pinnable, so don't complain about them\n        # not being pinned.\n        if not req.is_direct and not req.is_pinned:\n            raise HashUnpinned()\n\n        # If known-good hashes are missing for this requirement,\n        # shim it with a facade object that will provoke hash\n        # computation and then raise a HashMissing exception\n        # showing the user what the hash should be.\n        return req.hashes(trust_internet=False) or MissingHashes()\n\n    def _fetch_metadata_only(\n        self,\n        req: InstallRequirement,\n    ) -> BaseDistribution | None:\n        if self.legacy_resolver:\n            logger.debug(\n                \"Metadata-only fetching is not used in the legacy resolver\",\n            )\n            return None\n        if self.require_hashes:\n            logger.debug(","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/operations/prepare.py#L466-L502","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","solutions":["Pin every requirement to an exact version with '==' in the requirements file.","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.","Ensure no requirement uses '>=', '~=', '<', '*', or bare names when --require-hashes is on.","For transitive deps, pin them explicitly in the (hashed) requirements file."],"exampleFix":"# before (requirements.txt)\n--require-hashes\nrequests >=2.0\n\n# after\n--require-hashes\nrequests==2.31.0 \\\n  --hash=sha256:aaaa... \\\n  --hash=sha256:bbbb...","handlingStrategy":"validation","validationCode":"from pip._vendor.packaging.requirements import Requirement\n\ndef assert_all_pinned(requirements_text):\n    unpinned = []\n    for line in requirements_text.splitlines():\n        line = line.split(\"#\", 1)[0].strip()\n        if not line or line.startswith(\"-\"):\n            continue\n        try:\n            req = Requirement(line)\n        except Exception:\n            continue\n        specs = str(req.specifier)\n        if \"==\" not in specs or \"*\" in specs or \"~=\" in specs or \">\" in specs or \"<\" in specs.replace(\"==\",\"\"):\n            unpinned.append(line)\n    if unpinned:\n        raise SystemExit(f\"unpinned under --require-hashes: {unpinned}\")","typeGuard":"from pip._vendor.packaging.requirements import Requirement\ndef is_exact_pinned(line: str) -> bool:\n    try:\n        req = Requirement(line)\n    except Exception:\n        return False\n    return any(op == \"==\" for op, _ in req.specifier)","tryCatchPattern":null,"preventionTips":["Generate hashed requirements with pip-compile or pip freeze, never hand-edit floating specs.","Run a pre-commit lint that rejects unpinned requirements when hashes are required.","Keep transitive dependencies pinned in the hashed lockfile."],"tags":["hashes","require-hashes","pinning","reproducibility","security"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}