Repository navigation
Fix requirements.txt pin grammar split (#1365) - #1381
Conversation
Assisted-by: Claude Code:claude-opus-5-5
`socket-patch vendor` refused a pip pin written in the legacy parenthesised form, `six (==1.16.0)`, with a false "not pinned to ==1.16.0", while scan and hosted mode accepted the same line. Three readers each had their own idea of what an exact pin is. Add one `Requirement` parse in utils::requirements (name, extras, specifier without the `( … )` wrapper, marker, options, direct reference) and read every requirements pin through it: the lock inventory and lockfile discovery (`exact_pin`), the vendored planner and the hosted rewriter. Each keeps its own policy: vendored still refuses `===`, hosted still rewrites it as arbitrary equality. Fixes #1365 Assisted-by: Claude Code:claude-opus-5-5
Wire and revert `six (==1.16.0)`, `six (== 1.16.0)` and `six ( ==1.16 )` end to end through the vendored requirements planner. Refs #1365 Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the shared requirements grammar: - `six==0!1.16.0` and `six==v1.16.0` are exact pins again in the vendored and hosted readers, as PEP 440 reads them. The lock inventory still takes only a digit-led release for its purl. - A vendored file line that names the package but does not parse cleanly still counts as naming it, so it is refused, never skipped. - The hosted rewriter classifies each line from one parse, and an unterminated continuation (`requests \`) still warns as one. - `direct_reference` reads through the same parser. Refs #1365 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Ready for review at
Slack announcement pending (no Slack send tool in this run; the next run retries). Generated by Claude Code |
Final review briefWhat it does. Replaces three diverging requirements.txt parsers (vendored Risk: low–medium. Refactor of security-relevant parsing, but every pip-valid input traced gives the same result as main or a better one. Look here
Verified. Read the full diff and traced old vs new behaviour. Changes I made. None. Open questions (non-blocking).
Auto-merge is armed: approving sends it straight to the merge queue. Generated by Claude Code |
|
bugbot run Generated by Claude Code |
From review on #1381: - Hosted: count every line that names the package, parseable or not, so a pip-invalid second line keeps an unpinned entry ambiguous instead of rewriting the first. - Vendored: refuse a pin carrying a per-requirement option other than --hash (e.g. --config-settings). The rewritten wheel line would silently drop it. Refs #1365 Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Both open questions from the final review are fixed in da628dc, and both now fail closed:
Checks: core lib tests pass except the 4 known root-only read-only-dir tests. Clippy is clean. Generated by Claude Code |
|
BugBot review Generated by Claude Code |
The guard against dropping per-requirement options only looked at `--` tokens, so `-C` (pip's short --config-settings) after --hash still read as a clean pin and the rewrite dropped it. Any `-` token other than --hash now refuses the pin. Refs #1365 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Ready for review at
Generated by Claude Code |
|
[final reviewer] Disarmed auto-merge: the head moved from Generated by Claude Code |
Final review briefWhat it does. Replaces three diverging requirements.txt parsers (inventory/VEX Risk: low–medium. Parsing-only change on a security-relevant path; the new paths fail closed. Look here
Verified. Read the full diff, focusing on da628dc and 19d9534. Changes I made. None. Open questions (non-blocking, follow-up material).
Auto-merge is armed: approving sends it straight to the merge queue. Generated by Claude Code |
Hosted mode carries long `--` options over into the rewritten line but dropped a short one after --hash (`-C x=y`, pip's --config-settings). main refused such a line as ambiguous; refuse it again. Refs #1365 Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Fixed the first open question from the final review in 7abc54f. It was a regression this PR introduced, so it belongs here rather than in a follow-up. Hosted mode again refuses a line that carries a short option the rewrite would drop. Example: The second open question needs no change. Generated by Claude Code |
|
BugBot review 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 7abc54f. Configure here.
Final review brief (update for 7abc54f)What it does. Unchanged from the previous brief: one shared requirements.txt grammar for inventory/VEX, hosted redirect and vendored planning; fixes #1365. The only new commit since that brief, 7abc54f, makes hosted mode refuse (as Risk: low–medium (unchanged). The new commit only adds a fail-closed refusal. Look here
Verified. Read the 7abc54f diff. Changes I made. None. Open questions. None blocking. Still follow-up material: Auto-merge is armed: approving sends it straight to the merge queue. Generated by Claude Code |
|
Ready for review at
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1365
Summary
socket-patch vendorrefused a pip pin written in the legacy parenthesised form,six (==1.16.0), with a false "requirements.txt: six is not pinned to ==1.16.0". Scan and hosted mode accepted the same line. Every requirements.txt pin is now read by one grammar, so scan, hosted and vendored mode agree on what an exact pin is.Root cause
Three separate parsers decided whether a requirements line is an exact pin:
utils::requirements::exact_pin(lock inventory, VEX discovery)name_reregex +requirement_versionparse_requirement_line+scan_pinsThe vendored one kept the
( … )wrapper in the specifier, so(==1.16.0)failedpep440::is_exact_pin_ofand was read as a range.What changed
utils::requirements::Requirement+parse_requirement: name as spelled, extras, specifier without the( … )wrapper, marker, options (split quote-aware), direct reference.exact_version()/arbitrary_version()read the one version token.exact_pinanddirect_referenceare thin wrappers over it. The inventory keeps its digit-led-version policy, since it writes the version into a purl.ParsedRequirement/parse_requirement_lineare deleted.scan_pinsreadsRequirement. A line that names the package but doesn't parse cleanly is still a refusal (fail closed).===stays a refusal by policy (code comment).name_reregex and the specifier parsing inrequirement_versionare deleted. Each line is classified from oneRequirementparse. TheUnpinned/Arbitrary/ direct-reference policy is unchanged.pep440::is_exact_pin(no callers left) is replaced bypep440::is_valid.exact_pinsix==1.16.0six (==1.16.0)six (== 1.16.0)six===1.16.0six==1.16.0,!=1.15Test evidence
Red→green, per issue:
six (==1.16.0)pin that the inventory and hosted mode accept, because exact pins are read by three grammars #1365:vendor::pypi_requirements::tests::find_pin_agrees_with_the_inventory_exact_pinfails onorigin/main(six (==1.16.0): left false, right true) and passes here.wires_and_reverts_a_parenthesised_pinwires and byte-identically revertssix (==1.16.0),six (== 1.16.0)andsix ( ==1.16 ).hosted_reads_pins_with_the_shared_requirements_grammarpins the hosted outcomes in the table, plus epoch andvpins and the unterminated-continuation warning.Commands (Linux, run as root):
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test --workspace --all-features --no-fail-fast: all pass except 13 tests that fail the same way without this change, for environment reasons:socket-patch-corepass when run as an unprivileged user.e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managedneeds patches-api.socket.dev, which the sandbox can't reach.e2e_vendor_pypi_build -- --include-ignored(uv 0.12.17): 52 passede2e_vex_build -- pip:: --ignored(pip 24, 26): 1 passednpm/,pypi/andgem/don't parse requirements./code-review high
Fixed: epoch and
v-prefix pins (0!1.16.0,v1.16.0) had stopped being exact. The hosted rewriter parsed each line twice.requests \\at EOF lost its continuation warning. Vendorednames_packagehad narrowed (fail-open).direct_referencewas a separate parser. The==format round-trip is gone.Not changed:
strip_comment). That matches pip's ownCOMMENT_RE((^|\s+)#.*$), so it is pip's reading, and it predates this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01BDvTPxPvcS1CjKoWyYSQzp
Generated by Claude Code