Repository navigation
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-dependenciesentry hosted mode added is removed (upstream_uv_override_removed)." An entry the user wrote before the scan should surviverollback/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 thegit checkoutremedy, 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:1086pushed_override: a spelling match on<name>==<version>, with no evidence the rewrite added it.crates/socket-patch-core/src/patch/redirect/upstream/uv.rs:1103restore_metadata(removes the entry, then the empty tables) and:947/:996(drops the[manifest] overridesentry on the same guess).
Probe run
https://github.com/SocketDev/socket-patch/actions/runs/36806727354 ("user override" groups in the Run probe step)
Activity
mikolalysenko commentedon Oct 1, 2026
[agent] Triaged:
priority:p1(uv). Not a duplicate, and no open or merged PR covers it. The cause ispushed_overrideinupstream/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] overridestogether with the hosted source), so it's not clustered with the pylock shape issues #407/#408.Generated by Claude Code
mikolalysenko commentedon Oct 5, 2026
[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.rollbackremove pkg:pypi/six@1.16.0Every cell: scan
redirected: 1, unwind exit 0 with warningsreinstall_requiredandupstream_uv_override_removed, and the user's own[tool.uv] override-dependencies = ["six==1.16.0"]is deleted (diffagainst the pre-scan pyproject shows only those three lines removed). The lock's[manifest] overridesis gone too. None of the uv unwind changes merged since045d7ec(#789, #822, #841, #818) touchpushed_override.Generated by Claude Code
uv: run 20 ledger (#869 filed; #411/#564 re-checked)
mikolalysenko commentedon Oct 8, 2026
[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"], thenuv lock. After that:scan --mode hosted: exit 0. The user's override becomes the hostedsix @ https://…entry.uv lock --checkpasses and six is patched.scan --mode vendored(the takeover): exit 0, eventsupstream_uv_override_removed→vendor_takeover_reverted_redirect→pypi_uv_override_requires_uv_0_5_6. The restore drops the override entirely, becausepushed_overridereads it as hosted-owned. The vendored backend then adds its own override and records "no override before vendoring" in the ledger.vendor --revert: exit 0success. It faithfully restores that pre-vendor state, so the user's[tool.uv]/override-dependencies = ["six==1.16.0"]lines are gone for good (diffagainst the original pyproject shows only those 3 lines removed). The--dry-runof step 2 previews a cleansuccessand doesn't mention the removal.This path is worse than the direct
rollback/removecase, because the deletion lands in the vendored ledger as the baseline. A laterremoveorrollbackof the vendored package can't bring the pin back either.uv lockkeeps six at 1.16.0 only because the lock still prefers it;uv lock --upgradewould 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
mikolalysenko commentedon Oct 9, 2026
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.
mikolalysenko commentedon Oct 9, 2026
[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
Keep user uv override pins on hosted rollback
Merge origin/main into agent/v5-uv-direct-refs