uv Tool Git Pins and Self-Update¶
Problem¶
dotfiles update --mine reported syncer already at latest (4.3.0) while syncer itself, run a
minute later, announced v5.3.0 — eight releases newer. The update phase was not lying about what it
observed. It ran uv tool upgrade syncer, which exits 0 having changed nothing, so the before/after
version comparison correctly found no movement.
The cause is in uv's receipt (~/.local/share/uv/tools/<tool>/uv-receipt.toml), which records the
requirement a tool was installed from:
requirements = [{ name = "syncer", git = "https://github.com/datapointchris/syncer.git?rev=v6.0.0" }]
uv tool upgrade re-resolves the requirement it finds. A pinned tag resolves to the same commit
every time, so upgrade is a permanent no-op for that tool — and it stays silent about being one.
The pin was written by the tool itself. pyselfupdate reinstalls as <tool> @ git+<url>@<tag>, so
the first syncer update rewrote a receipt that dotfiles had installed unpinned, and removed the
tool from the fleet updater's reach for good.
Solution¶
Pin deliberately, in both directions, rather than fighting over the receipt. git_uv_tools are now
installed as <name> @ git+<repo>@<newest release tag> by both uv-tools.sh (fresh install) and
update.sh (update), via install/common/lib/uv-git-tools.sh. Moving a pin means reinstalling the
requirement; uv tool upgrade cannot do it. Both dotfiles and the tools' own updaters resolve the
version from the GitHub releases API, so they cannot disagree about which release is newest.
tracks_branch: true in packages.yml marks the exception — a repo publishing no releases has no tag
to pin, so it follows its default branch and is upgraded with uv tool upgrade.
Key Learnings¶
- An unpinned git install is not the neutral default; it is a distinct, degraded state.
pyselfupdatetreats a git requirement with norev=as a dev checkout: it never prints an update notice and refuses to reinstall over it. Five tools installed by dotfiles had their own update notices silently disabled, which is why only syncer ever nagged. - "Already at latest" needs to mean "the newest release was resolved and compared to this one." Reporting off a before/after diff of local state was an improvement over reporting off an exit code, but it still cannot see a no-op that was never going to do anything.
- Pinning to releases means unreleased commits on
mainare deliberately not installed. Cutting a release is what ships work to the fleet. The migration moved three tools back frommainHEAD to their release tag; all the skipped commits wereci:/docs:/style:. - Two updaters writing the same receipt must agree on its shape. Whichever ran last won, and the loser reported success either way.