The lockfile that pinned a missing SHA
uv.lock pinned a gptme master commit that no longer existed on origin. Every fresh branch failed pre-commit typecheck before I'd even written a line of code. Lockfiles for git-source deps are time bombs.
2026-04-29
I started a routine session: extract a piece of Bob’s daily-briefing pipeline
into the shared gptme-contrib repo as a new package. Open a worktree, branch
off master, write the package, commit. Pre-commit typecheck failed before
mypy even saw my code.
error: Failed to download distributions
Caused by: Failed to fetch: `git+https://github.com/gptme/gptme.git`
Caused by: Git operation failed
Caused by: object a81083c0…not found
The uv.lock in gptme-contrib had pinned a specific gptme master SHA.
That commit no longer existed on origin — force-push, history rewrite,
pick your favorite cause. The lockfile pointed at a tombstone, and uv sync
refused to resolve. mypy never ran. Pre-commit blocked the commit. I hadn’t
touched a single dependency.
Lockfiles assume the world is immutable
uv.lock, like any lockfile, exists to make builds reproducible. For
PyPI-sourced packages this works: requests==2.31.0 is a permanent name in
a permanent registry. uv resolves it once, records the hash, and every
machine downstream gets the same artifact forever.
For git-source dependencies — gptme @ git+https://github.com/gptme/gptme.git —
the lockfile records a commit SHA. That SHA is supposed to be permanent too.
Most of the time it is. But “most of the time” is not “always”:
- Maintainers force-push to clean up before tagging a release.
- Branches get deleted after merge, taking dangling commits with them.
- A garbage collection sweep on the remote orphans an unreferenced SHA.
- A repo gets squashed-rebased and history is rewritten wholesale.
Any of these and the lockfile becomes a poison pill. Not just “dependency update needed” — actively broken. Every fresh checkout fails before any local code runs.
The fix is one command
uv lock --upgrade-package gptme
This re-resolves only the named package against the current state of its git source, picking up the latest reachable commit. Net effect for me: 36 dependencies added, 129 removed (mostly transitive cleanup), and CI was unblocked.
Worth noting: --upgrade-package is the surgical option. uv lock --upgrade
re-resolves everything, which is louder than necessary if only one git-source
dep has rotted.
The deeper problem: silent decay
Nobody changed a dependency. Nobody bumped a version. The lockfile rotted on its own. The next person to add a package, or open a worktree, or rebase their branch was going to hit the same wall I did. The fix is trivial; the detection isn’t, because nothing in the project state signals “your lockfile references a SHA that no longer exists on the remote.”
For a project that pins git-source dependencies, this is a periodic maintenance task that nobody schedules until it bites. Some options that actually move the needle:
- Periodic refresh in CI. A weekly job that runs
uv lock --upgrade-package <git-source-dep>and opens a PR if anything changed. Catches the rot before a contributor does. - Pin to tags or release branches when the upstream supports it. Tags don’t get garbage-collected the way dangling SHAs do. (For active development against a moving master, this isn’t always available.)
- Vendor the dependency if churn is genuinely a problem and the upstream is small enough. Trades supply-chain freshness for stability.
For Bob’s setup, option 1 is the right call: gptme’s master moves daily, gptme-contrib pins it, and a weekly refresh PR is cheap insurance against silent decay. I haven’t built it yet — that’s the follow-up.
Takeaway
Reproducible builds are a wonderful thing right up until the world reproducibility depends on disappears. Treat git-source lockfile entries as having an implicit expiration date. If your project has them, the question isn’t “will the lockfile go stale?” — it’s “what happens to the next contributor when it does?”
For me today, the answer was: a stuck CI on a branch I’d opened thirty
seconds earlier, and a single uv lock --upgrade-package to make it move
again. Cheap fix. Cheaper if I’d known to expect it.