Skip to content

🐛 fix(uv): rank minor version junctions behind the concrete install on Windows - #170

Merged
gaborbernat merged 3 commits into
tox-dev:mainfrom
darrenhuai:fix/uv-minor-link-order
Oct 28, 2026
Merged

gaborbernat merged 3 commits into
tox-dev:mainfrom
darrenhuai:fix/uv-minor-link-order

Conversation

@darrenhuai

@darrenhuai darrenhuai commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

On Windows, discovery in a uv store returned uv's minor version link (cpython-3.15-windows-x86_64-none) instead of the concrete install it points at (cpython-3.15.0a7-windows-x86_64-none). 🐛 uv moves that link to the next patch release on upgrade, so callers that store the path, such as a virtualenv's base interpreter, end up pointing at a different Python later. #112 ranked these links behind their target, and the docs promise that order, but the ranking only held on POSIX.

uv creates the links as symlinks on POSIX and as directory junctions on Windows, and a junction does not report as a symlink (Python only gained os.path.isjunction in 3.12). For a prerelease the link name parses as the final release and outranks the install; for an X.Y.0 install the two tie and the link wins on name order. The uv store ranking now spots a link by checking whether the entry resolves to a directory with a different name, which works for symlinks and junctions on every supported Python without a platform branch.

Installs that uv also registers under PEP 514 were mostly unaffected, since the registry lookup runs before the store. The fix matters for installs found only through the store, prereleases among them.

darrenhuai and others added 3 commits October 27, 2026 18:57
…Windows

uv links each minor version at its newest patch install, cpython-3.15 pointing at
cpython-3.15.0b3 for example, and tox-dev#112 ranks that link behind the install it points
at because uv repoints it on the next patch release. On Windows uv creates these
links as directory junctions, and Path.is_symlink() returns False for a junction on
every Python version, so the link was never demoted there. For a prerelease the link
parses as the final release and wins outright, and for an X.Y.0 install the two tie
and the link wins on directory name order. The resolved-path dedup then drops the
concrete install, so get_interpreter() and iter_interpreters() report the junction
path, which moves the next time uv installs a patch release. PEP 514 discovery runs
first and finds the concrete path for installs uv registered there, so this hits
Windows users whose uv Python has no registry entry.

Spot the link by comparing the name it resolves to with its own name instead.
Path.resolve() follows symlinks and junctions alike on every supported version, so
this needs no Windows-only API: os.path.isjunction only exists from 3.12 and the
stat reparse tag constants are only defined on Windows.
Move the platform branch out of the test body so the test only states
what it checks. Drop the test docstring; the source comment already
explains why junctions need their own detection.
@gaborbernat gaborbernat added the bug Something isn't working label Oct 28, 2026
@gaborbernat
gaborbernat merged commit 77f64b9 into tox-dev:main Oct 28, 2026
32 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants