You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Lockfile discovery treats a requirements.txt -r include as a competing lock, so after a hosted rewrite vex attests nothing (exit 2) and rollback refuses (exit 1), although pip install -r requirements.txt installs the patched wheel #1086
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
Take a pip project where the root requirements.txt pins six==1.16.0 and also includes -r dev.txt, and dev.txt pins six==1.16.0 too. Duplicate pins like this are common with split base/dev files. scan --mode hosted rewrites the root line to six @ <socket-hosted wheel>#sha256=… and leaves dev.txt alone, because hosted only rewrites the root file. It exits 0 with status: success and redirected: 1.
pip reads the root file and its includes as one requirement set. A direct URL plus a compatible ==1.16.0 specifier resolves to the URL, so pip install -r requirements.txt installs the patched wheel on pip 20.3.4, 24.0 and 26.2.1.
Lockfile discovery, however, treats dev.txt as a separate lock that "resolves the same version from elsewhere" (contest_across_locks). So:
vex drops the ref with patched_ref_unattributable, then fails with Error: Manifest not found … no hosted or vendored patch references were found … nothing to attest (exit 2). The error message is also misleading, since the reference is there.
rollback refuses the whole project: requirements.txt wire(s) Socket-hosted patches that cannot be attributed to one package version (the lockfiles disagree …), status: error, exit 1. The hosted pin can't be unpatched with socket-patch.
Re-running scan --mode hosted, the remedy the warning names, exits 0 with success and no warning, and changes nothing.
Impact
CI that gates on vex fails for a project whose pip install -r requirements.txt is actually patched.
rollback / remove can't undo a pin that scan wrote a moment earlier. The only way out is git checkout.
Open PR Decide whether a hosted patch is pinned through lockfile discovery alone #1058 adds a pre-write attribution gate built on this same discovery. With it, the hosted scan would skip this candidate as redirect_unattributable, and the project would never get patched. Fixing the contest rule first would avoid that.
Repro (Linux, main 05ecc6e, local mock patch API serving a patched six-1.16.0 wheel)
SP=target/release/socket-patch
API="--api-url $MOCK --org test-org --api-token fake --patch-server-url $MOCK"
mkdir proj &&cd proj
python3.11 -m venv .venv && .venv/bin/pip install -q pip==26.2.1 six==1.16.0
printf -- '-r dev.txt\nsix==1.16.0\n'> requirements.txt
printf'six==1.16.0\n'> dev.txt
$SP scan --yes --json --mode hosted --ecosystems pypi $API# exit 0, success, redirected 1, warnings: [redirect_pypi_stale_install]
.venv/bin/pip install --force-reinstall -r requirements.txt # installs the Socket wheel; `import six` -> patched$SP vex $API --product pkg:pypi/app@1.0.0 -O vex.json # exit 2:# Warning: requirements.txt: pkg:pypi/six@1.16.0 is wired to Socket patch <uuid>, but dev.txt resolves the# same version from elsewhere (not a Socket patch) …# Error: Manifest not found at ./.socket/manifest.json, and no hosted or vendored patch references were found …$SP rollback --yes --json $API# exit 1, status error, "cannot be attributed to one package version"$SP scan --yes --json --mode hosted --ecosystems pypi $API# exit 0, success, no warning; state unchanged
The include line can come first or last (six==1.16.0\n-r dev.txt\n); both fail the same way.
Expected vs actual
Expected: CLI_CONTRACT.md line 370 lists the PyPI wiring as "requirements.txt + its in-root -r includes", which is one install tree. The "Contested locks" rule (line 382) is about another lock resolving the same name@version, where which lock the build uses "depends on the package manager that runs". Here there's only one package manager and one install (pip install -r requirements.txt), and it installs the patched wheel. The module docs in vex/discover/pypi_other.rs (lines 53–61) already treat the root and its includes as one tree read by the planner's own walk. vex should attest not_affected, and rollback should restore the root line. The include's six==1.16.0 should either not count as a contest within the same tree, or count only when it pins a version the URL doesn't satisfy.
Actual: each include is a separate "lock" in contest_across_locks, so a compatible duplicate pin vetoes the root's wiring. vex exits 2, rollback exits 1, and the re-scan remedy is a no-op.
OS × version
Cell
Result
Linux, pip 26.2.1 / py3.11, include before the root pin
fail (3/3: vex exit 2, rollback exit 1)
Linux, pip 26.2.1 / py3.11, include after the root pin
fail (1/1)
Linux, pip 20.3.4 / py3.8 and 24.0 / py3.11: pip install -r requirements.txt from a fresh venv
patched wheel installed (so the project is patched)
Control: no duplicate pin
pass (not_affected)
Control: duplicate pin in a sibling requirements-dev.txt the root never includes
pass (not_affected; siblings aren't read, by design)
Control: -c constraints.txt pinning six==1.16.0
pass (not_affected)
Control: the include has a range (six>=1.0), not an exact pin
pass (not_affected)
macOS / Windows: not probed. The logic is path-only and OS-independent.
First bad: this is not a regression from this week. Main 9c43dfc (2026-10-05) behaves the same (vex exit 2, rollback exit 1). v4.0.0 has no lockfile-based hosted vex / rollback to compare against.
Suspect code
crates/socket-patch-core/src/vex/discover/pypi_other.rs:257: every exact registry pin in every file of the include tree is recorded as resolved_elsewhere(file, …) under its own file name.
crates/socket-patch-core/src/vex/discover/mod.rs:698-711 (contest_across_locks): a ref is contested when e.file != r.source_file, so a root ref and an include pin of the same tree contest each other. Files of one requirements include tree should share a lock identity here, the way the planner and discovery already walk them as one.
Related, but different: #567 (Pipenv: Pipfile.lock vs a requirements include; those really are two package managers), #410 (all-hosted rollback, fixed), and PR #1058 (the attribution gate that would turn this into a skipped patch).
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
Take a pip project where the root
requirements.txtpinssix==1.16.0and also includes-r dev.txt, anddev.txtpinssix==1.16.0too. Duplicate pins like this are common with split base/dev files.scan --mode hostedrewrites the root line tosix @ <socket-hosted wheel>#sha256=…and leavesdev.txtalone, because hosted only rewrites the root file. It exits 0 withstatus: successandredirected: 1.pip reads the root file and its includes as one requirement set. A direct URL plus a compatible
==1.16.0specifier resolves to the URL, sopip install -r requirements.txtinstalls the patched wheel on pip 20.3.4, 24.0 and 26.2.1.Lockfile discovery, however, treats
dev.txtas a separate lock that "resolves the same version from elsewhere" (contest_across_locks). So:vexdrops the ref withpatched_ref_unattributable, then fails withError: Manifest not found … no hosted or vendored patch references were found … nothing to attest(exit 2). The error message is also misleading, since the reference is there.rollbackrefuses the whole project:requirements.txt wire(s) Socket-hosted patches that cannot be attributed to one package version (the lockfiles disagree …),status: error, exit 1. The hosted pin can't be unpatched with socket-patch.scan --mode hosted, the remedy the warning names, exits 0 withsuccessand no warning, and changes nothing.Impact
vexfails for a project whosepip install -r requirements.txtis actually patched.rollback/removecan't undo a pin thatscanwrote a moment earlier. The only way out isgit checkout.redirect_unattributable, and the project would never get patched. Fixing the contest rule first would avoid that.Repro (Linux, main
05ecc6e, local mock patch API serving a patchedsix-1.16.0wheel)The include line can come first or last (
six==1.16.0\n-r dev.txt\n); both fail the same way.Expected vs actual
requirements.txt+ its in-root-rincludes", which is one install tree. The "Contested locks" rule (line 382) is about another lock resolving the samename@version, where which lock the build uses "depends on the package manager that runs". Here there's only one package manager and one install (pip install -r requirements.txt), and it installs the patched wheel. The module docs invex/discover/pypi_other.rs(lines 53–61) already treat the root and its includes as one tree read by the planner's own walk.vexshould attestnot_affected, androllbackshould restore the root line. The include'ssix==1.16.0should either not count as a contest within the same tree, or count only when it pins a version the URL doesn't satisfy.contest_across_locks, so a compatible duplicate pin vetoes the root's wiring.vexexits 2,rollbackexits 1, and the re-scan remedy is a no-op.OS × version
pip install -r requirements.txtfrom a fresh venvnot_affected)requirements-dev.txtthe root never includesnot_affected; siblings aren't read, by design)-c constraints.txtpinningsix==1.16.0not_affected)six>=1.0), not an exact pinnot_affected)macOS / Windows: not probed. The logic is path-only and OS-independent.
First bad: this is not a regression from this week. Main
9c43dfc(2026-10-05) behaves the same (vex exit 2, rollback exit 1). v4.0.0 has no lockfile-based hostedvex/rollbackto compare against.Suspect code
crates/socket-patch-core/src/vex/discover/pypi_other.rs:257: every exact registry pin in every file of the include tree is recorded asresolved_elsewhere(file, …)under its own file name.crates/socket-patch-core/src/vex/discover/mod.rs:698-711(contest_across_locks): a ref is contested whene.file != r.source_file, so a root ref and an include pin of the same tree contest each other. Files of one requirements include tree should share a lock identity here, the way the planner and discovery already walk them as one.Related, but different: #567 (Pipenv: Pipfile.lock vs a requirements include; those really are two package managers), #410 (all-hosted rollback, fixed), and PR #1058 (the attribution gate that would turn this into a skipped patch).