Skip to content

Hosted uv rollback, remove and vendored takeover always refuse locks written by uv 0.6.15–0.6.17, which spell the artifact field upload_time instead of upload-time #788

Description

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

Summary

uv 0.6.15, 0.6.16 and 0.6.17 write lock revision = 2 with the artifact timestamp key spelled upload_time (underscore). uv 0.7.0 and later spell it upload-time, and uv 0.6.0–0.6.14 write no timestamp at all. The v5 upstream restore in patch/redirect/upstream/uv.rs accepts only upload-time as a sibling artifact field. On these locks every hosted unwind refuses with:

cannot restore pkg:pypi/six@1.16.0 to its upstream registry entry: uv.lock: sibling artifacts carry an unknown field `upload_time`; restore it from version control instead (`git checkout -- uv.lock`)

scan --mode hosted itself works on these locks (it wires the project, uv sync --locked installs the patched wheel, and vex attests). The patch can't be taken off again: rollback, remove and the hosted→vendored takeover all exit 1. Project locks and PEP 723 script locks are both affected. A pylock from uv pip compile on 0.6.17 uses the PEP 751 upload-time, and it rolls back byte-identically.

Impact

On a uv 0.6.15–0.6.17 project, a hosted patch can be applied but never unwound or migrated to vendored mode, apart from git checkout. A newer uv keeps the field as written until something re-locks, so a lock that 0.6.x wrote stays affected even after the user upgrades uv. docs/testing/uv-compatibility.md says lock revision 2 (0.6.15–0.8.3) is covered, and scripts/uv-vex-matrix.sh includes 0.6.17. CLI_CONTRACT's "Hosted unwind coverage" list of refusals (exclude-newer / no-binary / no-build, non-PyPI registries, [[distribution]]) doesn't include this case. This is a refusal that fires when it shouldn't.

Repro (Linux, real uv; mock patch API as in the bug-hunt harness)

pip install --target /tmp/uv0617 uv==0.6.17; UV="env PYTHONPATH=/tmp/uv0617 /tmp/uv0617/bin/uv"
mkdir app && cd app
printf '[project]\nname = "app"\nversion = "0.1.0"\nrequires-python = ">=3.9"\ndependencies = ["six==1.16.0", "idna==3.7"]\n' > pyproject.toml
$UV lock && $UV sync
grep -c upload_time uv.lock            # 4
cp pyproject.toml p.o; cp uv.lock l.o
socket-patch scan --mode hosted --yes --json ...   # exit 0, six wired
socket-patch rollback --yes --json ...             # exit 1: unknown field `upload_time`
socket-patch remove pkg:pypi/six@1.16.0 --yes ...  # exit 1: same
socket-patch scan --mode vendored --yes ...        # exit 1, redirect_revert_failed: same

The same steps with uv 0.7.22 restore byte-identically.

Expected vs actual

  • Expected: the upstream restore re-derives the six entry in the sibling's shape, using whichever key spelling the sibling uses. CLI_CONTRACT says artifacts are re-derived "in the artifact shape a sibling registry package shows (which of size / upload-time / … this uv release records)". The result is a byte-identical pyproject.toml / uv.lock, as on 0.7.x and later.
  • Actual: refused, exit 1, with the files still pointing at the hosted wheel.

Matrix (main 045d7ec)

uv lock key Linux macOS Windows
0.6.14 and earlier (none) pass – –
0.6.15 upload_time fail (rollback, twice) – –
0.6.16 upload_time fail (rollback, twice) – –
0.6.17 upload_time fail (rollback, remove, takeover, script lock) fail (all 4) fail (all 4)
0.7.0 / 0.7.22 / 0.9.30 / 0.10.12 / 0.11.33 / 0.12.23 upload-time pass pass (0.7.22) pass (0.7.22)

Probe run: https://github.com/SocketDev/socket-patch/actions/runs/37210059602

First bad

The unreleased v5 upstream restore. The known field check was added in de316b4 (#358). The released v4.0.0 predates the uv upstream restore.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/upstream/uv.rs:466: let known = ["url", "hash", "hashes", "size", "upload-time", "name"]; has no upload_time.
  • uv.rs:402 / uv.rs:419 / uv.rs:520 read and render only upload-time. The renderer also needs to write the key in the sibling's spelling.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions