Skip to content

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

Closed

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.0 and list all exit 1 with hosted_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 own uv 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_contested

The 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

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::stale is fed only by crate::vex::discover::StaleSelector, which only vex/discover/yarn.rs:414 produces. Nothing produces a stale pin for a uv override-dependencies / sources pair whose package has left uv.lock, so HostedInventory::of (:192) puts the uuid in contested.
  • 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-authored override-dependencies = ["<pkg>==<ver>"] pin that hosted mode never added #411), which is the attribution such a stale pin would need.

Activity

  1. mikolalysenko commented on Oct 12, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged: priority:p1 (uv / PyPI family). Confirmed the suspect path on main (74a44b8): HostedInventory::stale (patch/redirect/upstream/mod.rs:184) is fed only by vex::discover::StaleSelector, and the only producer is yarn berry discovery (vex/discover/yarn.rs:414), so a uv override-dependencies + [tool.uv.sources] pair whose package has left uv.lock lands in contested. 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 about dynamic dependencies, #1105 about the takeover venv probe).


    Generated by Claude Code

  2. mikolalysenko commented on Oct 12, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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

  3. mikolalysenko commented on Oct 12, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1453


    Generated by Claude Code

  4. added 4 commits that reference this issue on Oct 12, 2026
    4db1fcc
    fd0b40e
    6e0b62e
    f6af097
  5. mikolalysenko commented on Oct 13, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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 + attrs and transitive six hosted into s.py.lock, then uv remove --script s.py isodate. After that, rollback pkg:pypi/six@1.16.0 exits 1 with hosted_wiring_contested. The script's tool.uv.sources.six URL 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. rollback and remove exit 0 with hosted_resolution_orphaned, leave no Socket URL in the lock or metadata, and uv lock --check passes. list exits 0 and reports the orphan. One thing the PR leaves alone: a later scan --mode vendored on 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, since rollback / remove now clean it up.


    Generated by Claude Code

  6. added a commit that references this issue on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions