Skip to content

Hosted uv rollback and remove delete a user-authored override-dependencies = ["<pkg>==<ver>"] pin that hosted mode never added #411

Description

[agent] Found by the scheduled uv bug-hunt routine (ledger #310).

Summary

On a uv project whose pyproject.toml already pins a transitive dependency with [tool.uv] override-dependencies = ["six==1.16.0"], a hosted scan leaves that override alone and only adds the [tool.uv.sources] url. rollback and remove pkg:pypi/six@1.16.0 then delete the user's own override anyway. They also drop the now-empty [tool.uv] table and the lock's [manifest] overrides entry, and they warn upstream_uv_override_removed ("removed the six==1.16.0 override-dependencies entry the hosted run adds"), which isn't true here.

v5 hosted mode keeps no ledger, so the upstream restore guesses ownership: pushed_override treats any override-dependencies entry spelled exactly <name>==<version> for a transitive dependency as the one the rewrite added (crates/socket-patch-core/src/patch/redirect/upstream/uv.rs:1086). A user who pinned that exact version themselves can't be told apart, and loses the pin.

Impact

This silently deletes user-authored resolver configuration. The lock still names 1.16.0 right after the rollback, so nothing fails at once. But the next uv lock --upgrade (or any re-resolve) moves six to 1.17.0, which the user had explicitly overridden away from. The warning tells them hosted mode added the line, so they have no reason to put it back. An exact-version override of a transitive dep is the usual way to hold back a problematic transitive release, and a project with such a pin is a natural candidate for scan --mode hosted.

Repro

Uses a local mock of the patch API serving a hosted six-1.16.0 wheel (the same mock as #379 / #381, now also returning integrity.sha512), plus --patch-server-url for the mock origin. The PyPI JSON API must be reachable (in the sandbox I used a local forwarder via SOCKET_PYPI_JSON_API; the probe runs hit pypi.org directly).

SP="socket-patch --api-url http://127.0.0.1:18080 --api-token t --org test-org --patch-server-url http://127.0.0.1:18080"
mkdir p && cd p
cat > pyproject.toml <<'TOML'
[project]
name = "uvp"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = ["python-dateutil==2.8.2"]

[tool.uv]
override-dependencies = ["six==1.16.0"]
TOML
uv lock
cp pyproject.toml pyproject.orig
$SP scan --mode hosted --json --yes     # redirected 1; vs pyproject.orig it adds ONLY [tool.uv.sources] six = { url = … }
$SP rollback --json --yes               # exit 0, success; warnings: reinstall_required, upstream_uv_override_removed
diff pyproject.orig pyproject.toml      # -[tool.uv]  -override-dependencies = ["six==1.16.0"]
grep -c '^overrides' uv.lock            # 0 (was 1)
uv lock --upgrade && grep -A1 'name = "six"' uv.lock   # version = "1.17.0"
# Control, same pyproject, no socket-patch: uv lock --upgrade keeps six 1.16.0.
# `remove pkg:pypi/six@1.16.0 --json --yes` instead of rollback behaves the same.

Expected vs actual

  • Expected: CLI_CONTRACT.md, "Hosted unwind coverage" (pypi): "A transitive override-dependencies entry hosted mode added is removed (upstream_uv_override_removed)." An entry the user wrote before the scan should survive rollback / remove, as it does in vendored mode (whose ledger records the original). If ownership can't be decided without a ledger, the restore should keep the entry, or refuse with the git checkout remedy, rather than delete it.
  • Actual: the user's entry is deleted, along with [tool.uv] and the lock's [manifest] overrides. Exit 0 / success, and a warning that blames hosted mode.

OS × uv matrix (main 2463257)

Each cell covers both rollback and remove. "fail" = user override deleted and uv lock --upgrade → six 1.17.0. The no-socket-patch control keeps 1.16.0 in every cell.

OS uv 0.2.37 uv 0.5.31 uv 0.12.21
Linux (sandbox) fail fail (2 runs) fail (2 runs)
macOS arm64 (probe) – fail fail
Windows (probe) fail fail fail

First bad

2463257 (#277, the v5 upstream restore). Release 4.0.0 reverted from a recorded ledger fragment and has no upstream restore.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/upstream/uv.rs:1086 pushed_override: a spelling match on <name>==<version>, with no evidence the rewrite added it.
  • crates/socket-patch-core/src/patch/redirect/upstream/uv.rs:1103 restore_metadata (removes the entry, then the empty tables) and :947 / :996 (drops the [manifest] overrides entry on the same guess).

Probe run

https://github.com/SocketDev/socket-patch/actions/runs/36806727354 ("user override" groups in the Run probe step)

Activity

  1. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged: priority:p1 (uv). Not a duplicate, and no open or merged PR covers it. The cause is pushed_override in upstream/uv.rs, which decides ownership by spelling alone. Without a ledger, a pre-existing <name>==<version> override can't be told apart from one the hosted run added, so the restore should keep the entry, or refuse, rather than delete it. That needs a design decision on what evidence of ownership to require (for example, the lock's [manifest] overrides together with the hosted source), so it's not clustered with the pylock shape issues #407/#408.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 5, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Re-triage from the uv bug-hunt routine (ledger #310): this still reproduces on main 99f61d2. I ran the repro from the issue body against the local mock on Linux.

    uv rollback remove pkg:pypi/six@1.16.0
    0.5.31 fail fail
    0.8.17 fail fail
    0.12.23 fail fail

    Every cell: scan redirected: 1, unwind exit 0 with warnings reinstall_required and upstream_uv_override_removed, and the user's own [tool.uv] override-dependencies = ["six==1.16.0"] is deleted (diff against the pre-scan pyproject shows only those three lines removed). The lock's [manifest] overrides is gone too. None of the uv unwind changes merged since 045d7ec (#789, #822, #841, #818) touch pushed_override.


    Generated by Claude Code

  3. added a commit that references this issue on Oct 5, 2026
  4. mikolalysenko commented on Oct 8, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Re-triage from the uv bug-hunt routine (ledger #310) on main 77f305d: this still reproduces. I also found a third path to the same data loss: the hosted → vendored takeover.

    Setup: dependencies = ["python-dateutil==2.9.0.post0"] plus the user's own [tool.uv] override-dependencies = ["six==1.16.0"], then uv lock. After that:

    1. scan --mode hosted: exit 0. The user's override becomes the hosted six @ https://… entry. uv lock --check passes and six is patched.
    2. scan --mode vendored (the takeover): exit 0, events upstream_uv_override_removed → vendor_takeover_reverted_redirect → pypi_uv_override_requires_uv_0_5_6. The restore drops the override entirely, because pushed_override reads it as hosted-owned. The vendored backend then adds its own override and records "no override before vendoring" in the ledger.
    3. vendor --revert: exit 0 success. It faithfully restores that pre-vendor state, so the user's [tool.uv] / override-dependencies = ["six==1.16.0"] lines are gone for good (diff against the original pyproject shows only those 3 lines removed). The --dry-run of step 2 previews a clean success and doesn't mention the removal.
    uv hosted takeover revert user override after revert
    0.5.31 0 0 0 deleted
    0.8.17 0 0 0 deleted
    0.12.23 0 0 0 (×2) deleted

    This path is worse than the direct rollback / remove case, because the deletion lands in the vendored ledger as the baseline. A later remove or rollback of the vendored package can't bring the pin back either. uv lock keeps six at 1.16.0 only because the lock still prefers it; uv lock --upgrade would now move it to 1.17.0. All runs were on Linux (it's pure text planning, with nothing OS-specific), with real uv and the local mock patch server.


    Generated by Claude Code

  5. added
    v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.
    compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.
    on Oct 9, 2026
  6. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    v5 release blocker (P1). A normal hosted scan/rollback must not delete a pre-existing uv override authored by the user.

    This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.

  7. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming for v5 blocker burn-down (shared root cause: hosted uv upstream restore decides override-dependencies ownership by spelling alone). Branch: agent/v5-uv-override-ownership. Claim-ID: 20261009T164135Z-ece7ea

  8. added a commit that references this issue on Oct 9, 2026
    6eefaf2
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

    agent:claimedagent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentcompatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.pm:uvuvpriority:p1v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions