ci(python): fail the build on a stale uv.lock - #2315
Open
contextablemark wants to merge 1 commit into
Open
Conversation
Nothing validated Python lockfiles. Every --frozen-lockfile check in CI is pnpm, which is how three released packages drifted -- ag_ui_adk five releases deep -- without a single red build (#2313 repaired it, #2314 removes the cause). Adds --locked to the nine `uv sync` steps rather than nine new `uv lock --check` steps: uv sync --locked asserts the lockfile is already in step with pyproject.toml and exits 1 if not, so the gate rides along with a command CI already runs. Measured on the aws-strands package -- with the lock's self-version knocked back a patch it exits 1 with "The lockfile at `uv.lock` needs to be updated, but `--locked` was provided", and exits 0 once restored. Safe to merge as-is: verified that all eight uv-managed packages this workflow covers currently pass `uv lock --check` against main, so no job goes red on landing. Note the check is resolution-level, not byte-level -- it tolerates a lock whose metadata representation is merely older than what today's uv would emit, and fails only when the lock genuinely disagrees with pyproject. That is the right sensitivity here: the version drift this exists to catch trips it, and a uv upgrade alone does not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Python Preview PackagesVersion
Install with uvAdd the TestPyPI index to your [[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = trueThen install the packages you need: # Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1785887008' --index testpypi
# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1785887008' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1785887008' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1785887008' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1785887008' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1785887008' --index testpypiInstall with pippip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
ag-ui-protocol==0.0.0.dev1785887008
Commit: 7e453fa |
@ag-ui/a2a-middleware
@ag-ui/a2ui-middleware
@ag-ui/event-throttle-middleware
@ag-ui/mcp-apps-middleware
@ag-ui/mcp-middleware
@ag-ui/a2a
@ag-ui/adk
@ag-ui/ag2
@ag-ui/agno
@ag-ui/aws-strands
@ag-ui/claude-agent-sdk
@ag-ui/claude-managed-agents
@ag-ui/crewai
@ag-ui/langchain
@ag-ui/langgraph
@ag-ui/llamaindex
@ag-ui/mastra
@ag-ui/pydantic-ai
@ag-ui/watsonx
@ag-ui/vercel-ai-sdk
@ag-ui/a2ui-toolkit
create-ag-ui-app
@ag-ui/client
@ag-ui/core
@ag-ui/encoder
@ag-ui/proto
commit: |
contextablemark
added a commit
that referenced
this pull request
Aug 5, 2026
uv.lock carries an entry for the package it locks -- the one whose source is
{ editable = "." } -- so editing pyproject.toml alone leaves that entry one
version stale. prepare-release.ts did exactly that, so every release shipped a
stale lock: four consecutive aws-strands releases are each a one-line, one-file
commit, and ag_ui_adk drifted five releases deep before anyone noticed. #2313
repaired the accumulated drift; this stops it recurring.
Runs `uv lock`, which is what the tooling is for. Note it may also flush latent
metadata corrections unrelated to the bump: it rewrites the whole file once it
has any reason to, and what it writes reflects the package metadata in uv's cache
at that moment. Measured -- the same uv 0.9.24 binary added an exceptiongroup
dependency marker on Aug 4 that it had not added on Jul 30 from an identical
starting state, because the cache had refreshed from PyPI in between. Those are
corrections rather than corruption (exceptiongroup genuinely needs
typing-extensions only below 3.13), and the companion `uv lock --check` CI gate
(#2315) keeps them from accumulating: with locks continuously current, a release
bump has nothing extra to flush and its diff stays to the version line.
Packages with no uv.lock (poetry-managed, or unlocked) are skipped; a missing uv
is fatal rather than skipped, since silently publishing a stale lock is the
failure this exists to prevent. uv's stdout is discarded so the script's JSON
summary stays parseable, with stderr passed through for diagnostics.
Testability: repoRoot was pinned to the script's own location, so a non-dry-run
would have edited the real repo and --dry-run (which never writes) was the only
safe mode -- leaving the write path uncovered. It now honours
PREPARE_RELEASE_ROOT, so a test can point config, package files and lockfiles at
a throwaway tree. The added test builds a dependency-free fixture package (so
`uv lock` needs no network), seeds its lock with a real uv run rather than
hand-writing one, bumps it through the actual script, and asserts pyproject.toml
and uv.lock both land on the new version. Commenting out the re-lock makes it
fail with "uv.lock self-entry not re-locked", so it guards the regression rather
than the implementation.
The test skips itself when uv is absent, so test-release-scripts.yml gains the uv
install -- otherwise it would pass vacuously in CI and give false confidence.
Rejected: a main-guard refactor to make the script importable. A guard whose
condition silently mismatched would stop main() running at all -- a quiet no-op in
release tooling, worse than the bug being fixed. The env override achieves the
same testability with no effect on normal invocation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third and last of the lockfile-drift set — #2313 repaired the damage, #2314 removes the cause, this stops it recurring silently.
Why
Nothing validated Python lockfiles. Every
--frozen-lockfilecheck in CI is pnpm, which is how three released packages drifted —ag_ui_adkfive releases deep — without a single red build.What
Adds
--lockedto the nine existinguv syncsteps, rather than nine newuv lock --checksteps.uv sync --lockedasserts the lockfile is already in step withpyproject.tomland exits 1 if not, so the gate rides along with a command CI already runs.Measured on
integrations/aws-strands/python:uv sync --lockeduv.lockneeds to be updated, but--lockedwas provided"Safe to merge as-is
Verified all eight uv-managed packages this workflow covers currently pass
uv lock --checkagainstmain, so nothing goes red on landing:sdks/python,aws-strands,adk-middleware,langgraph,crew-ai,watsonx,langroid,claude-managed-agents— all PASS.On sensitivity
The check is resolution-level, not byte-level. It tolerates a lock whose metadata representation is merely older than what today's uv would emit, and fails only when the lock genuinely disagrees with
pyproject.toml. That's the right sensitivity: the version drift this exists to catch trips it, while a uv upgrade alone doesn't turn every PR red.🤖 Generated with Claude Code