[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/[email protected] 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/[email protected] --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)
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
On a uv project whose
pyproject.tomlalready pins a transitive dependency with[tool.uv] override-dependencies = ["six==1.16.0"], a hostedscanleaves that override alone and only adds the[tool.uv.sources]url.rollbackandremove pkg:pypi/[email protected]then delete the user's own override anyway. They also drop the now-empty[tool.uv]table and the lock's[manifest] overridesentry, and they warnupstream_uv_override_removed("removed thesix==1.16.0override-dependencies entry the hosted run adds"), which isn't true here.v5 hosted mode keeps no ledger, so the upstream restore guesses ownership:
pushed_overridetreats anyoverride-dependenciesentry 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) movessixto 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 forscan --mode hosted.Repro
Uses a local mock of the patch API serving a hosted
six-1.16.0wheel (the same mock as #379 / #381, now also returningintegrity.sha512), plus--patch-server-urlfor the mock origin. The PyPI JSON API must be reachable (in the sandbox I used a local forwarder viaSOCKET_PYPI_JSON_API; the probe runs hit pypi.org directly).Expected vs actual
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.[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
rollbackandremove. "fail" = user override deleted anduv lock --upgrade→ six 1.17.0. The no-socket-patch control keeps 1.16.0 in every cell.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 probestep)