Repository navigation
Fix Poetry rewrite of a source = "pypi" dep (#1282) - #1413
Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A dependency the pyproject pins to a named source, such as
`six = {version = "1.16.0", source = "pypi"}`, leaves no trace in
poetry.lock. Hosted and vendored scans rewrote it to a URL or file
source anyway, and Poetry 1.4 to 2.2 then refuse every install of that
lock ("Repository \"pypi\" does not exist.") while the scan reported
success.
Both rewriters now read the constraint from the pyproject next to the
lock (main, legacy dev and group tables; table, inline and
multiple-constraint forms). When the lock was written by Poetry 1.4 to
2.2, the package is refused before any write
(redirect_poetry_lock_unsupported hosted, pypi_poetry_source_constraint
vendored). Locks from other Poetry releases are still wired, with a
redirect_poetry_source_constraint / pypi_poetry_source_constraint
warning naming the affected range.
Fixes #1282
Assisted-by: Claude Code:claude-opus-5-5
Adds a real-Poetry e2e that pins six with `source = "pypi"`, runs a hosted and a vendored scan, and checks a fresh `poetry install` still works: on Poetry 1.4 to 2.2 the lock is left untouched, elsewhere it installs the patched wheel. CI already runs this suite on Poetry 1.0.10, 1.1.15, 1.8.5, 2.0.1 and 2.4.3. Documents the refusal and the new warning codes in CLI_CONTRACT.md and docs/testing/poetry-compatibility.md. Refs #1282 Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the source-pin guard: - A vendored to hosted takeover now refuses up front when hosted mode would refuse the package, so it stays vendored instead of being reverted into neither mode. - A lock an earlier run already rewired is refused (hosted) or no longer read as in sync (vendored) when Poetry 1.4 to 2.2 wrote it, since its installs already fail. - `[[tool.poetry.dependencies.<name>]]` multiple constraints are read. - The pyproject is parsed once per lock, the lock lookup no longer renders the lock, messages name the pyproject next to the lock, and the Poetry dependency-table walk is shared with classify_dependency. - CLI_CONTRACT.md documents pypi_poetry_source_constraint. Refs #1282 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Dropping the `source` key or upgrading Poetry both mean re-locking, which throws away whatever the lock already wires. When the package is still vendored (takeover, or an already-wired lock) the refusal now says to run `socket-patch vendor --revert` first and that it reverts every vendored package; over an existing hosted pin it says to run `socket-patch rollback` first. A fresh refusal keeps the short remedy. Refs #1282 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
A bare `socket-patch rollback` undoes every patch in the project, so the source-pin refusal over an existing hosted pin now names `socket-patch rollback pkg:pypi/<name>@<version>` and says that the bare form would undo everything. Refs #1282 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Ready for review (burn-down agent)
Generated by Claude Code |
Final review briefWhat it does. A dependency pinned in pyproject with Risk: low-medium. Every new path only refuses or warns, before any write, and is keyed narrowly on a source pin plus the writer stamp. The medium part is policy: the 1.4-2.2 range is a measured claim, and the stamp names the Poetry that wrote the lock, not necessarily the one that installs it. Look here
Verified
Changes I made: none. Open questions (non-blocking)
Auto-merge is armed, so approving sends it straight to the merge queue. Generated by Claude Code |
A package pinned to a private index already carries that index as a `[package.source]` in poetry.lock, and hosted mode refused it as a user-authored source before #1282. The new source-pin check took that case over and told the user to drop the `source` key, which is wrong for a private index. The check now treats only socket-patch's own pins (a vendored `.socket/vendor/pypi/` wheel or a hosted `/patch/pypi/` URL) as already wired. Any other existing source falls through to the original "refusing to replace an existing Poetry source" refusal. Refs #1282 Assisted-by: Claude Code:claude-opus-5-5
|
[agent] On the final review's open questions:
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 64d03c1. Configure here.
Final review brief (replaces the brief for 9c56814)What it does. Fixes #1282. Poetry writes nothing to Risk: low to medium. The gate only fires when the pyproject has a Look here
Verified. Read the full diff, then the delta since the last brief more closely. Ran the Poetry core lib tests: 127 passed. The 1 failure is a chmod-0555 test that can't fail as root, the same one the PR description names. Mutation checks:
Changes I made. None. I disarmed auto-merge when 64d03c1 landed after the previous brief, and I re-arm it with this brief. Open questions (non-blocking; they only affect the remedy text, not data safety).
Auto-merge is armed, so approving sends it straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1282
Summary
Poetry lets a dependency name the source it must come from, for example
six = {version = "1.16.0", source = "pypi"}. Poetry's docs recommend this when a project also has private sources. Before this PR, hosted and vendored scans rewired such a dependency, reported success, and left a lock that Poetry 1.4–2.2 can't install: every laterpoetry installfailed withRepository "pypi" does not exist.socket-patch no longer writes that lock.Root cause
Both Poetry lock rewriters only check the lock's own
[package.source]:patch/redirect/poetry.rs, on top ofutils::poetry_lockvendor/pypi_poetry.rs::check_target_guardsPoetry writes nothing into
poetry.lockfor a package pinned to PyPI, so the rewriter sees an ordinary registry entry. Poetry 1.4–2.2 then compare the pyprojectsourcewith the rewrittenurlorfilelock source and refuse the install. 1.1–1.3 and 2.3+ install it (measured matrix in #1282).Fix
utils::poetry_lock::pyproject_source_constraintsreads the per-dependencysourcefrom the pyproject next to the lock. It covers:dev-dependenciesand every group table[[tool.poetry.dependencies.x]]multiple-constraint formsVendored
classify_dependencynow uses the same dependency-table walk (poetry_dependency_entries).Lock stamped
@generated by Poetry 1.4–2.2: the package is refused before any write.redirect_poetry_lock_unsupported. The uuid isn't confirmed, so there's no VEX attestation.pypi_poetry_source_constraint(failed), before the wheel is built.pypi_takeover.rs), so the package stays vendored instead of being reverted into neither mode.Any other writer (1.1–1.3, 2.3+, unstamped): the rewrite goes ahead as before, with a warning that names the 1.4–2.2 range (
redirect_poetry_source_constraint/pypi_poetry_source_constraint).Lock entry already has a source socket-patch didn't write (a private index, or the user's own path or URL): the original "refusing to replace an existing Poetry source" refusal applies, as before this PR. The source-pin check counts only socket-patch's own
.socket/vendor/pypi/and/patch/pypi/pins as already wired.Remedy text: drop the
sourcekey or upgrade to Poetry ≥ 2.3, re-lock, and re-run. Re-locking discards whatever the lock already wires, so:socket-patch vendor --revert, and that it reverts EVERY vendored packagesocket-patch rollback pkg:pypi/<name>@<version>Documented in
CLI_CONTRACT.mdanddocs/testing/poetry-compatibility.md.Why gate on the writer instead of always refusing: the stamp names the Poetry that wrote the lock, which is usually the one that installs it. Refusing every source-pinned dep would stop patching on 2.3+, where it works. Only warning on every writer would keep breaking 1.4–2.2 builds. The issue offers both "refuse" and "at least warn", and this uses each where it fits.
Tests (red → green)
redirect::poetry::…::source_constrained_dependency_is_refused_for_poetry_1_4_to_2_2(14 lock generations × 6 pyproject forms, sibling lock, already-redirected re-run, remedy order)redirect::poetry::…::private_index_pin_keeps_the_user_authored_source_refusalvendor::pypi_poetry::tests::source_constrained_dependency_is_refused_for_poetry_1_4_to_2_2(incl. already-wired lock, revert-first remedy)pypi_takeover::tests::poetry_source_pinned_package_follows_the_writer(revert-first remedy)utils::poetry_lock::tests::pyproject_source_constraints_reads_every_declaration_form,source_constraint_breaks_only_poetry_1_4_to_2_2_writerse2e_vex_build poetry::poetry_source_pypi_constraint_never_breaks_install: real Poetry, hosted + vendored scan, then a freshpoetry installmust succeed, with the patched bytes when rewiredCI already runs
e2e_vex_build poetry::on Poetry 1.0.10, 1.1.15, 1.8.5, 2.0.1 and 2.4.3. The new e2e returns early on 1.0, which predates per-dependency sources.Local runs:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test -p socket-patch-core --all-features: every core integration test passes. 4 lib tests fail, all chmod-based and unable to fail as root in this sandbox:copy_tree::relax_loop…,vlt_heal::an_unremovable…,pypi_poetry::wire_write_failure…andpypi_requirements::wire_failure…. Fix gem revert of a commented Gemfile line (#988) #1406, Fix Composer vendor-dir config cascade (#439, #686) #1408 and Fix hosted cargo pin of registry = "crates-io" (#386) #1412 carry the same note.SOCKET_PATCH_POETRY_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_vex_build -- --ignored poetry::: 4 passed on Poetry 1.8.5 and on 2.4.3cargo test -p socket-patch-cli --all-features --test mode_migration_pypi: 58 passednpm/,pypi/andgem/only dispatch to the binary.PR CI on 64d03c1:
ci-okand every other check green (35 passed, 19 skipped by path filters). Bugbot found no new issues.Review
/code-review highfindings, fixed in df01bb4:Unchanged/InSync) read as confirmed or in sync[[tool.poetry.dependencies.x]]formBugbot findings:
Final-review finding: a private-index pin keeps its original refusal (64d03c1).
Not fixed:
files. The source message is accurate on its own, and the other reason shows on the next run. Folding the gate intorewrite_poetry_lock_allas aLockStep::Refusedwould report both, but that touches everyPoetryLockDepcaller, which is more refactoring than this fix needs.utils::poetry_lock, so the two modes can't drift. Moving it into the engine is the same refactor as above.🤖 Generated with Claude Code
https://claude.ai/code/session_01BbSVVjLH4F68h9WmHJAaX1