Repository navigation
Fix orphaned hosted uv override wiring (#1445) - #1453
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Hosted uv wires a transitive package with a marked override-dependencies entry plus a [tool.uv.sources] URL. When the parent stops depending on it (uv remove <parent>, uv lock --upgrade-package <parent>), uv drops the package from uv.lock but keeps both lines and the [manifest] overrides record. Discovery could not attribute that wiring, so rollback, remove and list refused it as contested forever, and the suggested re-scan changed nothing. uv discovery now reports such a pair as a stale pin, the same way yarn berry reports a leftover resolutions selector (#1203). The uv restore retires the override, the source and the lock's manifest record, leaving a lock `uv lock --locked` accepts. A user's own (unmarked) override, or a package the lock still holds, stays contested. Fixes #1445 Assisted-by: Claude Code:claude-opus-5-5
list now names the pyproject.toml override pin a relock left behind instead of describing it as a yarn resolutions entry, and the CLI contract documents the uv case of hosted_resolution_orphaned. Regression tests cover list, rollback (unscoped and by purl) and remove (by uuid and purl) over the leftover wiring. Refs #1445 Assisted-by: Claude Code:claude-opus-5-5
The transitive hosted uv lane now drops the parent that pulled six in and relocks with the uv under test, which leaves the hosted override, source and manifest record behind. list must list around it, rollback must retire it, and a following `uv lock` must keep the lock as is. Refs #1445 Assisted-by: Claude Code:claude-opus-5-5
A leftover override is now retired only when its source and every [manifest] record of the package still name the pin's patch; otherwise the pin is refused with the reason, rather than removing the override alone and leaving a lock uv rejects. A paired metadata file that can't be read is now named in the refusal too. Also resolve a lock's paired metadata path through one helper, and share the unconditional [tool.uv.sources] lookup in discovery. Refs #1445 Assisted-by: Claude Code:claude-opus-5-5
|
Ready for review (burn-down agent)
Generated by Claude Code |
Final review briefWhat it does: After hosted mode pins a transitive uv package, removing or upgrading the parent ( Risk: low to medium. A retire happens only when all of these hold: the override carries the hosted marker, the package isn't declared directly, the package is gone from the lock, the source is a single unconditional hosted URL naming that exact release, and the source and manifest URLs carry the pin's uuid. A user's own overrides, and wiring that belongs to another patch, stay contested, and tests cover both. Look here:
Verified:
Changes I made: none. Open questions (non-blocking):
Auto-merge (squash) is armed, so approving sends this straight to the merge queue. Generated by Claude Code |
|
Thanks for the brief. On the three open questions:
No changes pushed; head stays Generated by Claude Code |
LLM Description written by Claude Code
Fixes #1445
Summary
After hosted mode pins a transitive uv package, removing or upgrading
its parent (
uv remove isodate,uv lock --upgrade-package isodate) usedto lock the project out of socket-patch:
rollback,removeandlistfailed with
hosted_wiring_contestedevery time, and the suggested re-scanchanged nothing. Now that leftover wiring counts as Socket's own leftover
pin, as yarn berry's leftover
resolutionsselectors already do (#1203).listlists around it with ahosted_resolution_orphanedwarning, androllback/removeretire it, leaving a lockuv lock --lockedaccepts.
Root cause
Hosted uv wires a transitive package with an
override-dependenciesentryunder the rewrite's
# socket-patch hostedcomment, plus a[tool.uv.sources]URL. uv.lock records the pair under[manifest] overrides. When the parent stops depending on the package, uvdrops its
[[package]]but never edits the override or the source. Indiscovery the hosted URL was then recognized but had no lock entry to
attribute it to, so
HostedInventory::offiled it undercontested. Theorphan path (
HostedInventory::stale) was fed only by yarn berrydiscovery.
Changes
utils::python_script::hosted_override_pins: one shared reading of"the marked
<name>==<version>overrides the hosted run added for apackage not declared directly". The uv restore's
pushed_overridenowuses it, and so does discovery.
vex::discover::pypi_locks: for a uv / script lock, each such overridewhose package the lock no longer holds, and whose single unconditional
source is a hosted URL naming that release's artifact, is recorded as a
StaleSelectorwith the lock as its file.HostedInventory::of: a stale uv pin's URL is excused in the lock and inits paired metadata.
upstream::uv::retire_orphaned_overrides: removes the override, thesource and the lock's
[manifest]record (dropping emptied tables), witha
hosted_resolution_orphanedwarning. Nothing is fetched. It refuseswith the reason when the source or a manifest record names another patch,
or when the metadata can't be read.
list: the warning names the metadata file and saysoverride-dependenciespin, notresolutionsentry.CLI_CONTRACT.md: documents the uv case.These stay contested, with tests: a user's own (unmarked) override, and a
package the lock still holds (a half-reverted pair).
Tests: per-issue checklist
uv remove <parent>oruv lock --upgrade-package <parent>drops a hosted-pinned transitive package, its leftover override makes rollback, remove and list fail forever with hosted_wiring_contested #1445crates/socket-patch-core/tests/hosted_inventory.rs):uv_override_left_by_relock_is_stale_not_contested,uv_override_left_beside_a_live_hosted_pin_is_stale,restore_retires_a_leftover_uv_override(byte-exact pyproject and lock;the expected lock is what real uv writes, and it passes
uv lock --locked). Guards:users_own_uv_override_left_by_relock_stays_contested,uv_override_still_in_the_lock_is_not_stale,leftover_uv_override_is_not_retired_against_another_patchs_record.Red on main: the first 3 failed; green with the fix.
in_process_rollback_hosted.rs):uv_override_left_by_relock_is_listed_around,rollback_retires_a_uv_override_left_by_relock(unscoped and by purl),remove_retires_a_uv_override_left_by_relock(by uuid and by purl).Red on main: 3/3 failed (run with main's
src/); green: 41/41.e2e_redirect_uv_build, transitive hosted lane, neworphan-unwindstep): drop the parent,uv lock, thenlistexit 0with the warning,
rollbackexit 0, and a followinguv lockkeeps thelock byte for byte. uv 0.11.32 locally: red on main (
list must list around the leftover pin), green with the fix. CI runs the lane onits uv legs.
Local gate on
f6af097:cargo fmt --checkandcargo clippy --workspace --all-features -D warningsclean. Incargo test --workspace --all-features,13 tests failed; the same tests fail on
mainin this sandbox. 12 of themcheck write or remove failures that root bypasses (the sandbox runs as uid 0).
The 13th,
e2e_bun_lockb, can't reachpatches-api.socket.dev. None of themtouch this diff. The real-uv
orphan-unwindstep passes onf6af097.Review notes
I ran
/code-review highon the diff and fixed: retiring when the sourceor
[manifest]record names another patch (now refused, with a test);swallowed metadata errors (now in the refusal); three spellings of the
paired-metadata path (now one
paired_metadata_pathhelper); theduplicated unconditional-source lookup (now
unconditional_source); andbuilding the package-name set for pylocks.
Not changed:
(
rewrite_project_metadata).hosted_override_pinskeeps the exactcheck the restore already used (
declares_directly), so nothing thatdecides ownership changes here. Merging it with the rewriter's check is
a separate refactor.
restore_lockto skip a second parse. Theextra parse happens only when an unhandled pin remains for that lock, and
a separate pass keeps
restore_lock's refusal ordering as it was.kindfield onStaleSelector. The filename-based split now goesthrough
paired_metadata_pathin every consumer. A typed kind wouldchange a public struct in core for one extra consumer.
🤖 Generated with Claude Code
https://claude.ai/code/session_019BeSCbNmwztgigM9uxRTHd
Note
Medium Risk
Changes hosted-mode discovery and rollback/remove behavior for uv lockfiles and pyproject metadata; incorrect classification could retire user overrides or leave contested state, but guards and tests cover unmarked overrides and in-lock packages.
Overview
Fixes #1445: leftover hosted uv wiring after a parent dependency is removed or upgraded no longer traps projects in
hosted_wiring_contested.When hosted mode pins a transitive package via a marked
[tool.uv] override-dependenciesentry and[tool.uv.sources]URL (withuv.lock[manifest] overrides),uv remove/uv lock --upgrade-packagecan drop the package from the lock while leaving those lines. The PR classifies that as Socket’s own orphan pin (same idea as yarn berry#1203resolutionsleftovers): discovery records it as aStaleSelector, inventory treats it as stale not contested,listwarns withhosted_resolution_orphaned(uv-specific wording), androllback/removeretire the override, source, and manifest record offline—refusing when metadata is unreadable or manifest/source point at another patch.Shared helpers
hosted_override_pinsandpaired_metadata_pathcentralize detection;CLI_CONTRACT.mddocuments the uv case. User unmarked overrides and packages still in the lock stay contested.Reviewed by Cursor Bugbot for commit f6af097. Configure here.
Generated by Claude Code