Correct the release skill's tag-trigger model and commands (#550)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+125
-30
@@ -1,13 +1,15 @@
|
||||
---
|
||||
name: release
|
||||
description: Prepare and publish stable Agent Lightning releases through the repository's version bump, pull-request checks, merge, tag, and PyPI trusted-publishing workflow. Use when asked to plan, cut, verify, or explain a release; treat nightly TestPyPI builds as a separate path.
|
||||
description: Prepare and publish stable Agent Lightning releases through the repository's version bump, pull-request checks, merge, tag, PyPI trusted-publishing, and versioned-documentation workflows. Use when asked to plan, cut, verify, or explain a release; treat nightly TestPyPI builds as a separate path.
|
||||
---
|
||||
|
||||
# Release Agent Lightning
|
||||
|
||||
Merging a pull request does not publish a stable release. Stable publication is
|
||||
triggered only by pushing a `v*` tag to the canonical repository; the tagged
|
||||
commit is what gets tested, built, and uploaded.
|
||||
commit is what gets tested, built, and uploaded. That same tag push also deploys
|
||||
versioned documentation and moves the public `stable` alias, so a release has two
|
||||
public side effects, not one.
|
||||
|
||||
## Establish the release state
|
||||
|
||||
@@ -17,16 +19,24 @@ commit is what gets tested, built, and uploaded.
|
||||
their URLs; do not assume particular remote names.
|
||||
3. Inspect the release contract in:
|
||||
- `.github/workflows/pypi-release.yml`
|
||||
- `.github/workflows/docs.yml`
|
||||
- `.github/workflows/tests.yml`
|
||||
- `scripts/bump_version.sh`
|
||||
- `pyproject.toml`
|
||||
- `agentlightning/__init__.py`
|
||||
4. Query the canonical repository's tags and compare them with the versions
|
||||
4. Confirm the canonical default branch is already green before branching from
|
||||
it. A release branch inherits every failure that main is carrying.
|
||||
5. Query the canonical repository's tags and compare them with the versions
|
||||
published at `https://pypi.org/pypi/agentlightning/json`. Confirm the target
|
||||
exists in neither place. If a tag is not represented on PyPI, or the proposed
|
||||
bump would skip an unpublished version, stop and obtain an explicit release
|
||||
decision. Never reuse or move a public release tag; PyPI versions are
|
||||
immutable.
|
||||
5. Treat verified PyPI trusted-publisher configuration for the canonical
|
||||
version exists in neither place, and stop for an explicit release decision
|
||||
when either of these holds:
|
||||
- A tag exists with no matching PyPI version. A published version is
|
||||
immutable, and its tag must never be reused or moved. A tag that never
|
||||
published is a different situation and still needs a human decision,
|
||||
informed by why it did not publish. See "Recovering a tag that never
|
||||
published" below.
|
||||
- The proposed bump would skip a version that was tagged but never published.
|
||||
6. Treat verified PyPI trusted-publisher configuration for the canonical
|
||||
repository and `pypi-release.yml` as a prerequisite. If it cannot be
|
||||
inspected directly, require confirmation from an authorized PyPI project
|
||||
owner before pushing the release tag.
|
||||
@@ -42,61 +52,146 @@ git switch -c chore/release-vX.Y.Z <canonical-remote>/<default-branch>
|
||||
scripts/bump_version.sh patch # or minor / major
|
||||
```
|
||||
|
||||
The bump script must keep these representations synchronized:
|
||||
The bump rewrites exactly three files. Confirm that with `git diff --stat`:
|
||||
|
||||
- `pyproject.toml`
|
||||
- the Agent Lightning entry in `uv.lock`
|
||||
- `agentlightning.__version__`
|
||||
- the `agentlightning` entry in `uv.lock`
|
||||
- `agentlightning.__version__` in `agentlightning/__init__.py`
|
||||
|
||||
Other version strings in the tree, such as the FastAPI `version` in
|
||||
`agentlightning/server/app.py`, are deliberately outside the bump. Leave them
|
||||
alone; changing them is a separate pull request, not release work.
|
||||
|
||||
Review the version diff, but do not run the release tests or package build
|
||||
locally. The pull request's GitHub checks are the verification gate.
|
||||
locally as a matter of course. `tests.yml` runs the same test set and package
|
||||
build on the pull request that `pypi-release.yml` will run on the tag, so the
|
||||
pull request's GitHub checks are the verification gate. Reproduce a single
|
||||
failure locally only when the workflow logs are not enough to fix it.
|
||||
|
||||
Commit the version change, push it to the fork, and open the pull request with
|
||||
the GitHub CLI when those external actions are authorized:
|
||||
|
||||
```bash
|
||||
git commit -am "Bump version to X.Y.Z"
|
||||
git push -u <fork-remote> <release-branch>
|
||||
gh pr create --repo OWNER/REPO \
|
||||
--base <default-branch> \
|
||||
--head <fork-owner>:<release-branch>
|
||||
--head <fork-owner>:<release-branch> \
|
||||
--title "Bump version to X.Y.Z" \
|
||||
--body "Prepare the vX.Y.Z release."
|
||||
```
|
||||
|
||||
Commit the version change, push it to the fork, and open the pull request with
|
||||
the GitHub CLI when those external actions are authorized. Use `gh pr checks
|
||||
--watch` to follow the pull request through its required checks. If a check
|
||||
fails, inspect it with `gh run view --log-failed`, correct the source on the same
|
||||
branch, and resume watching. Once every required check has succeeded, merge the
|
||||
pull request with `gh pr merge` using a merge method the repository permits.
|
||||
Merging remains a distinct external action and requires authorization.
|
||||
`gh pr create` refuses to run without `--title` and `--body` outside an
|
||||
interactive terminal, and every `gh` call needs `--repo OWNER/REPO` so it acts
|
||||
on the canonical repository rather than the fork.
|
||||
|
||||
Follow the pull request through its required checks with
|
||||
`gh pr checks <pr> --repo OWNER/REPO --watch`. If a check fails, take the run id
|
||||
from that output, inspect it with
|
||||
`gh run view <run-id> --repo OWNER/REPO --log-failed`, correct the source on the
|
||||
same branch, and resume watching. Once every required check has succeeded, merge
|
||||
the pull request with `gh pr merge <pr> --repo OWNER/REPO` using a merge method
|
||||
the repository permits. Committing, pushing, opening the pull request, and
|
||||
merging are each distinct external actions and each requires authorization.
|
||||
|
||||
## Tag and publish the merged release
|
||||
|
||||
After the pull request merges, update the local default branch from the
|
||||
canonical repository and use the resulting commit as the release candidate:
|
||||
canonical repository, then confirm that the commit you are about to tag is the
|
||||
one this pull request produced and not a later commit that landed behind it:
|
||||
|
||||
```bash
|
||||
git switch <default-branch>
|
||||
git pull --ff-only <canonical-remote> <default-branch>
|
||||
gh pr view <pr> --repo OWNER/REPO --json mergeCommit
|
||||
git rev-parse HEAD
|
||||
```
|
||||
|
||||
Verify that the checked-out package version and runtime `__version__` both equal
|
||||
`X.Y.Z`. Immediately query the canonical repository and PyPI again to ensure
|
||||
that `vX.Y.Z` is still absent. Then create an annotated tag on the checked-out
|
||||
merge commit and push it to the canonical repository:
|
||||
If HEAD has moved past the merge commit, tag the merge commit explicitly instead
|
||||
of HEAD.
|
||||
|
||||
`pypi-release.yml` fails the release when the packaged version does not equal the
|
||||
tag without its leading `v`, or when it does not equal the runtime
|
||||
`__version__`. Check both before tagging:
|
||||
|
||||
```bash
|
||||
git tag -a vX.Y.Z -m "vX.Y.Z"
|
||||
uv version --short
|
||||
grep '^__version__' agentlightning/__init__.py
|
||||
```
|
||||
|
||||
The workflow itself reads the runtime value as
|
||||
`python -c 'from agentlightning import __version__; print(__version__)'`, after
|
||||
`uv sync` has installed the checkout. Locally that import can resolve to some
|
||||
other installed copy of the package instead of the tree being tagged, so read
|
||||
the file directly here; `agentlightning/__init__.py` assigns `__version__` as a
|
||||
single literal, so the two agree by construction.
|
||||
|
||||
GitHub reads workflow files as they exist **at the tagged commit**, not at the
|
||||
tip of the default branch. Confirm that the commit being tagged actually
|
||||
contains `.github/workflows/pypi-release.yml` with its `v*` trigger; a commit
|
||||
that predates the workflow will never publish, however the tag is pushed.
|
||||
|
||||
Immediately query the canonical repository and PyPI again to ensure that
|
||||
`vX.Y.Z` is still absent. Then create an annotated tag on the release commit and
|
||||
push it to the canonical repository:
|
||||
|
||||
```bash
|
||||
git tag -a vX.Y.Z -m "vX.Y.Z" <release-commit>
|
||||
git push <canonical-remote> vX.Y.Z
|
||||
```
|
||||
|
||||
The tag push starts the production PyPI publication, so obtain explicit
|
||||
authorization immediately before it. Use `gh run list` and `gh run watch
|
||||
--exit-status` to follow the `PyPI Release` workflow through its terminal result.
|
||||
After success, verify that PyPI exposes the exact version and both the expected
|
||||
wheel and source distribution, then report the workflow URL and PyPI outcome.
|
||||
authorization immediately before it.
|
||||
|
||||
## Follow both tag-triggered workflows
|
||||
|
||||
One tag push starts two workflows, and both belong to the release:
|
||||
|
||||
- `PyPI Release` (`pypi-release.yml`) re-checks the version against the tag,
|
||||
runs the tests, builds the wheel and source distribution, and uploads them to
|
||||
PyPI through trusted publishing.
|
||||
- `Deploy Documentation` (`docs.yml`) runs
|
||||
`mike deploy --push --update-aliases X.Y.Z stable`, which publishes the
|
||||
versioned documentation and repoints the public `stable` alias at this
|
||||
release.
|
||||
|
||||
Follow both to a terminal result with `gh run list --repo OWNER/REPO` and
|
||||
`gh run watch <run-id> --repo OWNER/REPO --exit-status`. After `PyPI Release`
|
||||
succeeds, verify that PyPI exposes the exact version with both the expected
|
||||
wheel and source distribution. After `Deploy Documentation` succeeds, verify
|
||||
that the published site serves `X.Y.Z` and that `stable` resolves to it. A green
|
||||
PyPI job with a failed documentation job is a half-finished release: report both
|
||||
workflow URLs and both outcomes.
|
||||
|
||||
For a transient workflow failure, rerun only with authorization. For a source
|
||||
or workflow defect, do not move the public tag; prepare a corrective release
|
||||
version. A GitHub Release and release notes are optional, separate publication
|
||||
actions and must not be created unless requested.
|
||||
|
||||
## Recovering a tag that never published
|
||||
|
||||
Separate the mechanics from the policy before proposing a recovery.
|
||||
|
||||
The mechanics: pushing a tag that already exists and points at the same commit
|
||||
changes no ref, so it starts no workflow run. Creating a tag, moving one to a
|
||||
different commit, or deleting and recreating one does change the ref and does
|
||||
start a run. What that run executes is the workflow file at the tagged commit,
|
||||
so a tag on a commit from before `pypi-release.yml` existed starts no PyPI
|
||||
publication no matter how it is pushed. Run
|
||||
`git ls-tree --name-only <tag> .github/workflows/` before assuming a re-push
|
||||
would help.
|
||||
|
||||
The policy: never move or reuse a tag whose version is on PyPI. That version is
|
||||
immutable, so a re-run could only fail at upload, and consumers who already
|
||||
resolved the tag would silently get different code.
|
||||
|
||||
Between those, a tag that never published is a decision for a release owner,
|
||||
not a default action. Releasing the next version from a commit that carries the
|
||||
current workflow is usually simpler and always safer than resurrecting the old
|
||||
tag. Note that a non-publishing tag may still have had effects: `docs.yml` has
|
||||
carried the `v*` trigger for longer than `pypi-release.yml`, so an older tag can
|
||||
have deployed documentation and moved `stable` without ever reaching PyPI.
|
||||
|
||||
## Nightly distinction
|
||||
|
||||
`.github/workflows/pypi-nightly.yml` publishes timestamped `.dev` builds to
|
||||
|
||||
Reference in New Issue
Block a user