Fix requirements.txt writers ignoring pip hash mode (#376, #378) - #383
Open
Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
Open
Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
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 <[email protected]> Claude-Session: https://claude.ai/code/session_01GQoii5oP1pwcJh5mzzo1HU
Collaborator
Author
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a2dedc5. Configure here.
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 <[email protected]> Claude-Session: https://claude.ai/code/session_017aQf44e9818AbFKDYnuAHZ
This branch has not been deployed
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
Fixes #378
Summary
Hosted and vendored scans no longer break
pip install -ron a requirements.txt that has no hashes.setupno longer reports success after writing a line that pip will refuse in a hash-pinned requirements.txt.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. None of the three requirements.txt writers checked which mode the requirements set was in:patch/redirect/requirements.rs) and the vendored writer (vendor/pypi_requirements.rs::vendor_line) always added--hash. In an unhashed file that switches the mode on, and pip then refuses every other line (Hosted and vendored requirements.txt rewrites add a --hash to one line, which turns on pip's hash-checking mode and breakspip install -rfor every other unhashed line #376).setup'srequirements_add(setup/pypi/edit.rs) always appended an unpinned, unhashedsocket-patch[hook]. In a hashed file, pip refuses that line (setupappends an unpinned, unhashedsocket-patch[hook]line to a hash-pinned requirements.txt, sopip install -rfails in hash-checking mode #378).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.pip install -rfor every other unhashed line #376): a hashed file keeps--hashon the patched line, as before. An unhashed file getsname @ <url>#sha256=<hex>instead. Checked by hand:integrity_of).pip install -rfor every other unhashed line #376): the mode is taken from the whole requirements tree (the root file plus its-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.setupappends an unpinned, unhashedsocket-patch[hook]line to a hash-pinned requirements.txt, sopip install -rfails in hash-checking mode #378): a hashedrequirements.txt, or one whose in-root-rinclude is hashed, now gets anerrorresult explaining hash-checking mode and is left untouched.setup --removenow works on pip's logical lines, so a hook line's\+--hashcontinuation lines are removed with it (the leftover noted insetupappends an unpinned, unhashedsocket-patch[hook]line to a hash-pinned requirements.txt, sopip install -rfails in hash-checking mode #378). The BOM is kept.--hashinto an unhashed set keeps that shape; runningrollbackfollowed by a re-scan rewrites it in the new form.Per-issue checklist
pip install -rfor every other unhashed line #376, hosted: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.pip install -rfor every other unhashed line #376, vendored:vendor::pypi_requirements::tests::unhashed_requirements_get_an_unhashed_vendor_line(red → green) andhashes_in_an_include_keep_the_vendor_line_hashed.pip install -rfor every other unhashed line #376, vendored rebuild guard:vendor::pypi::tests::in_sync_ledgerless_rebuild_of_unhashed_line_keeps_the_wired_path(red → green).pip install -rfor every other unhashed line #376, real pip: a newunhashedcell (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.setupappends an unpinned, unhashedsocket-patch[hook]line to a hash-pinned requirements.txt, sopip install -rfails in hash-checking mode #378:setup::pypi::edit::tests::test_add_refuses_hash_pinned_requirements,test_add_refuses_when_an_include_is_hash_pinnedandtest_requirements_remove_drops_continuation_lines(all red → green).test_add_hash_pinned_file_with_hook_is_configuredis the positive control.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.main(including #277) was merged in ata2dedc5.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.Follow-ups
setupwritessocket-patch[hook]into requirements.txt, but socket-patch-hook was never published to PyPI, so pip silently installs socket-patch 3.3.0 and no .pth hook #377 (thesocket-patch-hookdistribution isn't on PyPI) is release-side and labelledagent:needs-human. Once that distribution is published, a hashed hook line could be written instead of refusing.🤖 Generated with Claude Code
https://claude.ai/code/session_017aQf44e9818AbFKDYnuAHZ