Fix requirements.txt writers ignoring pip hash mode (#376) - #383
Merged
Mikola Lysenko (mikolalysenko) merged 9 commits intoOct 1, 2026
Merged
Conversation
Assisted-by: Claude Code:claude-opus-5-5
This was referenced Sep 30, 2026
A hosted or vendored scan added --hash to the patched line of a requirements file that had no hashes. pip then turns on hash-checking mode for the whole install, so every other requirement and every transitive dependency failed to install (#376). Both writers now check whether the requirements set is already in hash-checking mode. If it is, they keep writing --hash as before. If not, hosted pins the patched wheel with the url's #sha256= fragment, which pip still verifies, and vendored writes the committed wheel path without a hash. Assisted-by: Claude Code:claude-opus-5-5
socket-patch setup appended an unpinned, unhashed socket-patch[hook] line to requirements.txt even when the file (or an -r include) was in pip's hash-checking mode. pip then refused to install anything from it, while setup reported success (#378). setup now reports an error and leaves such a file untouched. setup --remove also drops a hook line together with its backslash continuation lines, so no stray --hash line is left behind. Assisted-by: Claude Code:claude-opus-5-5
Adds an 'unhashed' cell (six plus idna, no hashes) to the real-pip capstone. It asserts the wiring adds no --hash and that pip installs the patched six. Also updates the redirect fixture to the #sha256= url form written for unhashed files. Assisted-by: Claude Code:claude-opus-5-5
The production and vendored e2e legs write a one-line unhashed requirements.txt and then asserted a --hash pin, which is the #376 behavior itself. They now assert that no --hash is added. The hosted leg also asserts the #sha256= url pin, which pip and uv both verify. Assisted-by: Claude Code:claude-opus-5-5
- The VEX discovery golden now shows the #sha256= url on the hosted requirements ref. - The hosted get test expects the url-fragment pin. - The vendor ledger parity test allows the one intended change from the base binary: no --hash in an unhashed requirements.txt. - The marker e2e installs with --require-hashes, so its input is now hash-pinned the way pip-compile writes it. Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
September 30, 2026 22:17
Collaborator
Author
|
BugBot review Generated by Claude Code |
An earlier cargo fmt --all run reformatted about 55 files that this fix doesn't otherwise touch; main isn't rustfmt-clean and CI doesn't check formatting. This restores those files to main so the PR diff only holds the hash-mode change and its tests. Assisted-by: Claude Code:claude-opus-5-5
Collaborator
Author
|
BugBot review Generated by Claude Code |
Bring the pip hash-mode fix onto the v5 workflow from #277. Main removed the setup command, so the #378 hook-dependency guard in setup/pypi/edit.rs has no home and is dropped with the file. The #376 half still applies: hosted and vendored requirements.txt writers only emit --hash when the tree is already in pip's hash-checking mode. The hosted-get test docs keep main's "no ledger" wording with the fragment pin, and CLI_CONTRACT.md's requirements rows now describe the conditional --hash. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQoii5oP1pwcJh5mzzo1HU
Collaborator
Author
|
bugbot run Generated by Claude Code |
A requirements vendor line written into an unhashed requirements set has no --hash, so wired_pin_in returned no pin and a ledgerless in-sync rebuild skipped the guard entirely, including the path check. A rebuilt wheel at another filename would then leave the wired line pointing at a file that does not exist. wired_pin_in now pins the path of a hashless line with an empty sha256, and the guard treats an empty pinned sha256 as path-only. A hash that is present but malformed still pins nothing, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017aQf44e9818AbFKDYnuAHZ
Collaborator
Author
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit fcc4fcc. Configure here.
Collaborator
Author
|
[burn-down agent] Ready for review on
Generated by Claude Code |
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Oct 1, 2026
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-requirements-hash-mode
branch
October 1, 2026 16:46
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 2, 2026
Since #383, a hosted scan of a requirements.txt that is not in hash-checking mode pins the patched wheel with the url's #sha256= fragment rather than --hash, so the PEP 440 regression test now expects that form, matching the existing hosted pypi test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVbFzTeYSTvB5iKjY6FRg7
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 2, 2026
* Start fix for #475 Assisted-by: Claude Code:claude-opus-5-5 * Match requirements pins like pip does (PEP 440) A hand-written pin such as `six==1.16` installs six 1.16.0, but the hosted requirements.txt rewrite compared versions as raw strings and skipped it, so `scan` exited 0 and the project stayed unpatched. The vendored requirements writer and the Hatch rewriter refused the same pins as "not pinned". Add a small PEP 440 equality helper (zero-padded release segments, leading zeros, case and pre/post/dev spellings) and use it for `==` pins in all three writers. `===` keeps plain string equality, as PEP 440 defines it. Fixes #475 Assisted-by: Claude Code:claude-opus-5-5 * Test hosted get over a PEP 440-equivalent pin Mirrors the #475 repro end to end: `get <uuid> --mode hosted` over `requests==2.31`, `==2.31.0.0` and `Requests==02.31.0` must redirect the pin to the hosted wheel. Fails on main, passes with the fix. Assisted-by: Claude Code:claude-opus-5-5 * Expect #sha256= pin in the PEP 440 hosted-get test Since #383, a hosted scan of a requirements.txt that is not in hash-checking mode pins the patched wheel with the url's #sha256= fragment rather than --hash, so the PEP 440 regression test now expects that form, matching the existing hosted pypi test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVbFzTeYSTvB5iKjY6FRg7 --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #376
Summary
Hosted and vendored scans no longer break
pip install -ron a requirements.txt that has no hashes.Root cause
pip's hash-checking mode is all or nothing. It turns on for the whole install as soon as any requirement carries
--hash, and then every requirement, transitive dependencies included, has to be==-pinned and hashed. The hosted redirect (patch/redirect/requirements.rs) and the vendored writer (vendor/pypi_requirements.rs::vendor_line) always added--hash, without checking which mode the requirements set was in. In an unhashed file that switches the mode on, and pip then refuses every other line.Fix
utils::requirements::requires_hashes. It is true when any line has a--hashoption (any algorithm) or the file sets--require-hashes. Comments and URL#sha256=fragments don't count.--hashon the patched line, as before. An unhashed file getsname @ <url>#sha256=<hex>instead. Checked by hand:integrity_of).-rincludes). A hashed tree keeps the vendor line unchanged. An unhashed tree gets the committed wheel path with no--hash, because pip can't read a fragment on a bare path. The inventory, VEX and the in-sync ledger fallback already accept a hashless vendor line.wired_pin_inreturns an empty sha256 andpin_matcheschecks the path only). A rebuild under another filename is refused instead of leaving the line pointing at a missing file. Bugbot found this; it is fixed infcc4fcc.--hashinto an unhashed set keeps that shape; runningrollbackfollowed by a re-scan rewrites it in the new form.Checklist
redirect::requirements::tests::unhashed_file_pins_the_artifact_by_url_fragment_not_hash_optionandhashed_file_keeps_the_hash_option. Both were red before the fix and are green after.vendor::pypi_requirements::tests::unhashed_requirements_get_an_unhashed_vendor_line(red → green) andhashes_in_an_include_keep_the_vendor_line_hashed.vendor::pypi::tests::in_sync_ledgerless_rebuild_of_unhashed_line_keeps_the_wired_path(red → green).unhashedcell (six==1.16.0+idna==3.7) ine2e_vex_build::pip. Locally on pip 24.3.1 every hosted and vendored step passes: wire, install-patched, manifest-less VEX, re-scan.utils::requirements::tests::requires_hashes_reads_pip_hash_checking_mode.Test updates (intended behavior change)
Several existing tests expected
--hashin unhashed files, which is the #376 bug itself. They now expect the unhashed forms:redirect-pypi.json);in_process_get_hosted_ecosystems,e2e_hosted_production,e2e_vendored_productionande2e_vendor_pypi_build.Two tests needed more than a new expected string:
vendor_ledger_schema_e2e: the base-binary parity test now names this one intended difference. The legacy fixtures are unchanged, so the legacy-revert coverage stays.An earlier commit on this branch accidentally reformatted about 55 unrelated files, and
e0ed1aareverts them.mainisn't rustfmt-clean and CI doesn't check formatting, so this PR leaves formatting alone.Evidence
cargo clippy --workspace --all-features -- -D warnings: clean onfcc4fcc.cargo test --workspace --all-features(local, sandbox runs as root): every failure is a test that needs a write, removal or permission check to fail, which root bypasses. None exercises code this PR changes.SOCKET_PATCH_PIP_E2E_VERSIONS=24 SOCKET_PATCH_PIP_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_vex_build -- pip:: --ignored: passes for all 4 cells × 2 modes (pre-merge).fcc4fcc, and Bugbot found no new issues on it.🤖 Generated with Claude Code
https://claude.ai/code/session_017aQf44e9818AbFKDYnuAHZ
Note
Medium Risk
Changes how hosted and vendored
requirements.txtpins are written, which affects install behavior for mixed hashed/unhashed projects; behavior is heavily tested but wrong detection could weaken or break installs.Overview
Fixes pip breaking when
requirements.txtis patched in an unhashed tree (#376). Hosted and vendored rewrites no longer unconditionally add--hash, which turns on pip’s global hash-checking mode and rejects every other requirement.A shared
requires_hasheshelper detects whether the file (or included tree) is already in hash-checking mode. Hosted redirects pin integrity with a URL#sha256=fragment when the file is unhashed, and keep--hashwhen it is already hashed. Vendored wheel lines omit--hashin unhashed trees (path-only pin); the in-sync rebuild guard treats an empty sha256 as path-only viapin_matches.CLI contract, redirect/VEX fixtures, and e2e tests (including a new pip VEX
unhashedcell) are updated to match the new shapes.Reviewed by Cursor Bugbot for commit fcc4fcc. Configure here.