Repository navigation
Hosted uv: after uv remove <parent> or uv lock --upgrade-package <parent> drops a hosted-pinned transitive package, its leftover override makes rollback, remove and list fail forever with hosted_wiring_contested #1445
Description
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
Hosted scan wires a transitive PyPI package in a uv project with an override-dependencies = ["six==1.16.0"] entry plus a [tool.uv.sources] URL, and uv.lock records it under [manifest] overrides. When the user later drops the parent (uv remove isodate, or uv lock --upgrade-package isodate to a release that no longer depends on six), uv leaves both Socket lines in place, because uv never edits override-dependencies / sources for a package that just isn't resolved any more. From then on:
rollback,remove pkg:pypi/six@1.16.0andlistall exit 1 withhosted_wiring_contested, with nothing touched.- The remedy they print ("re-run
socket-patch scan --mode hosted") succeeds (exit 0) but changes nothing, so the next rollback refuses again. The other remedy (git checkout -- pyproject.toml uv.lock) throws away the user's ownuv remove/ upgrade.
So a user can't unwind Socket's hosted wiring from that project at all, short of hand-editing both files. This is the uv twin of #1203 (yarn berry resolutions left behind by yarn remove / yarn up), which was fixed with the HostedInventory::stale / hosted_resolution_orphaned path. That path is only fed by yarn berry discovery.
Repro
This uses the bug-hunt mock patch API (it serves a patched six 1.16.0 wheel; it's the same harness as #1287 / #1374).
mkdir orph && cd orph
cat > pyproject.toml <<'EOF'
[project]
name = "demo"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = ["isodate>=0.6"]
EOF
uv lock -P isodate==0.6.1 -P six==1.16.0 # isodate 0.6.1 depends on six
uv sync
SP="socket-patch --api-url $MOCK --api-token fake --org acme --patch-server-url $MOCK"
$SP scan --yes --json # success: override-dependencies six==1.16.0 + [tool.uv.sources] six = { url = ".../six-1.16.0-py2.py3-none-any.whl" }
uv sync --locked # six patched
uv lock --upgrade-package isodate # isodate 0.7.2 has no six; (or: uv remove isodate)
uv sync --locked # ok, six uninstalled; both Socket lines and [manifest] overrides remain
$SP rollback --yes --json # exit 1, error.code hosted_wiring_contested
$SP remove pkg:pypi/six@1.16.0 --yes --json # exit 1, hosted_wiring_contested
$SP list --json # exit 1, hosted_wiring_contested
$SP scan --mode hosted --yes --json # exit 0, success, files unchanged
$SP rollback --yes --json # exit 1 again, hosted_wiring_contestedThe refusal reads: pyproject.toml, uv.lock wire(s) Socket-hosted patches that cannot be attributed to one package version (the lockfiles disagree, or the reference is malformed), so socket-patch cannot manage them safely; reconcile the lockfiles (re-run socket-patch scan --mode hosted) or restore them from version control (git checkout -- pyproject.toml uv.lock).
Expected vs actual
- Expected: CLI_CONTRACT.md (the hosted rollback / eject section) says leftover hosted wiring that no live lock entry resolves any more, left behind because the package manager never edits it, "is not contested ... it is Socket's own leftover pin", and
rollback(unscoped or scoped to its purl) andremove <uuid|purl>retire it with ahosted_resolution_orphanedwarning (Afteryarn removeof a hosted-pinned yarn berry package, its leftoverresolutionspin makes rollback, remove and list fail forever with hosted_wiring_contested, and the remedy they print (re-run the hosted scan) changes nothing #1203). The uv equivalent is anoverride-dependenciesentry plus[tool.uv.sources]URL that the hosted run itself added (pushed_overridealready recognizes it) for a package that has no[[package]]in uv.lock any more. Rollback should retire both lines and the[manifest] overridesrecord, leaving uv.lock--locked-clean. At the very least, the printed remedy shouldn't be a no-op. - Actual: it's classed as contested wiring. rollback, remove and list exit 1 forever, and the suggested
scan --mode hostedloops.
Control (pass): a direct hosted six followed by uv remove six. uv deletes the source line itself, so nothing is left over.
Matrix (main 7236190, Linux, uv-managed Python 3.11)
| uv | uv lock --upgrade-package isodate |
uv remove isodate |
uv remove six (direct, control) |
|---|---|---|---|
| 0.5.31 | fail: rollback / remove / list exit 1 contested; remedy no-op | fail (same) | pass (no leftover) |
| 0.12.24 | fail | fail | pass |
| 0.13.0 | fail | fail | pass |
Each cell ran twice: the initial command, then again after following the printed remedy. 18/18 failing cells reproduce. The wiring shape is version-independent, so I expect macOS and Windows to behave the same (I didn't probe them).
Suspect code
crates/socket-patch-core/src/patch/redirect/upstream/mod.rs:184:HostedInventory::staleis fed only bycrate::vex::discover::StaleSelector, which onlyvex/discover/yarn.rs:414produces. Nothing produces a stale pin for a uvoverride-dependencies/sourcespair whose package has left uv.lock, soHostedInventory::of(:192) puts the uuid incontested.crates/socket-patch-core/src/patch/redirect/upstream/uv.rs:1617(pushed_override) already identifies the override entry the hosted run added for a transitive package (Hosted uv rollback and remove delete a user-authoredoverride-dependencies = ["<pkg>==<ver>"]pin that hosted mode never added #411), which is the attribution such a stale pin would need.
Activity
mikolalysenko commentedon Oct 12, 2026
[agent] Triaged:
priority:p1(uv / PyPI family). Confirmed the suspect path onmain(74a44b8):HostedInventory::stale(patch/redirect/upstream/mod.rs:184) is fed only byvex::discover::StaleSelector, and the only producer is yarn berry discovery (vex/discover/yarn.rs:414), so a uvoverride-dependencies+[tool.uv.sources]pair whose package has left uv.lock lands incontested. Not a duplicate: #1203 fixed the same orphan class for yarn berry only. No open PR covers it. It does not share a root cause with the other open uv issues (#639 is aboutdynamicdependencies, #1105 about the takeover venv probe).Generated by Claude Code
mikolalysenko commentedon Oct 12, 2026
[agent] Claiming this issue (shared root cause: uv hosted discovery never reports a Socket override/source pair whose package left uv.lock as a stale pin, so it is classed as contested). Branch: agent/fix-uv-hosted-orphaned-override. Claim-ID: 2026-10-12T22:21:01Z-f219e0
Generated by Claude Code
mikolalysenko commentedon Oct 12, 2026
[agent] Draft PR: #1453
Generated by Claude Code
Retire uv override pins a relock left behind
List and unwind leftover uv override pins
Cover the leftover uv override with real uv
Refuse a leftover uv pin whose wiring disagrees
mikolalysenko commentedon Oct 13, 2026
[agent] Two updates from the uv bug-hunt run on main
15c2c83.PEP 723 scripts hit this too. Setup: a script with
isodate==0.6.1+attrsand transitive six hosted intos.py.lock, thenuv remove --script s.py isodate. After that,rollback pkg:pypi/six@1.16.0exits 1 withhosted_wiring_contested. The script'stool.uv.sources.sixURL and the lock's override line are both left behind (uv 0.13.0).PR #1453 (head
f6af097) fixes both lanes. I tested project (uv remove isodate) and script (uv remove --script) × uv 0.5.31 / 0.13.0.rollbackandremoveexit 0 withhosted_resolution_orphaned, leave no Socket URL in the lock or metadata, anduv lock --checkpasses.listexits 0 and reports the orphan. One thing the PR leaves alone: a laterscan --mode vendoredon the project lane still exits 0 and keeps the orphaned hosted override/source lines, because six is no longer installed and there's nothing to vendor. That seems acceptable, sincerollback/removenow clean it up.Generated by Claude Code
chore(release): 5.0.1 (#1485)