Release Process¶
This document describes how gepa-adk releases are created and published to PyPI.
Overview¶
Releases are fully automated via release-please and GitHub Actions:
- Conventional commits on
mainare tracked automatically - release-please opens/updates a version-bump PR with changelog
- Merging the PR creates a
v*tag - publish.yml triggers on the tag: build → verify → attest → publish to PyPI → deploy docs
No manual version bumps, tags, or GitHub Releases are needed.
How Releases Work¶
Step 1: Merge conventional commits to main¶
Use conventional commit prefixes:
| Prefix | Bump | Example |
|---|---|---|
feat: | minor | feat(engine): add mutation rationale capture |
fix: | patch | fix(scorer): handle empty output |
feat!: or BREAKING CHANGE: | major | feat!: remove evolve_sync |
docs: | none | Shown in "Documentation" section of changelog |
chore:, test:, ci: | none | Hidden from changelog |
Step 2: release-please creates a PR¶
After each push to main, the release-please.yml workflow:
- Analyzes new commits since the last release
- Opens (or updates) a PR titled
chore(main): release X.Y.Z - Generates a changelog from conventional commits
- Bumps
pyproject.tomlversion via theextra-filesconfig - An
update-lockfilejob updatesuv.lockon the PR branch
Compatibility contract and cadence¶
Routine CI runs six test jobs: all Python 3.12/3.13 × ADK 1.39.1/locked combinations on Linux, Python 3.13 with locked ADK on macOS, and Python 3.12 with locked ADK on Windows. Four more macOS and Windows combinations run in full validation; they stay supported. Every selected leg retains the coverage floor. The empty ADK matrix value means the version in uv.lock, not the newest index release. CI prints and asserts Python and ADK versions in the pytest process; uv run --no-sync prevents the pinned override from being silently undone.
Full validation runs ten combinations: the Linux cross-product plus every macOS and Windows combination except Python 3.12 with ADK 1.39.1, which full mode drops on those two platforms. It runs:
- Weekly, Monday at 08:00 UTC on the default branch.
- For release-please PRs, and again at the exact release tag before the publish workflow can build or publish any artifact.
- For pushes to
mainthat changepyproject.toml,uv.lockor.python-version. A push with missing comparison history also selects full coverage. - On demand:
gh workflow run ci.yml --ref <branch> -f full=true. Use this before merging platform-sensitive changes.
Every other pull request runs routine coverage, Dependabot updates included; the planner does not inspect a pull request's diff. The accepted trade-off: a platform-only wheel break from a dependency bump surfaces on the push to main, not on the pull request.
The CI plan job prints the selected mode and reason. A stable CI gate fails if planning, any quality job or any selected test job fails, is cancelled or is skipped. Require this check in repository rules instead of matrix-job names that are absent on routine runs. Retain other required repository checks. A failed weekly run requires triage on the existing issue and a passing full check before release; it is not a waiver for unsupported combinations. The release supervisor owns this decision and links the exact successful revision/run in the release PR. No live API-tier calls are added.
For a deliberate routine measurement, dispatch with -f full=false; this opt-in measures six legs even on a workflow branch. It is not full compatibility evidence. Manual runs report Manual CI gate, so they cannot replace the required PR check. Compare PR/main lifecycle job counts and summed job seconds; separate billing-export runner minutes from timestamp-derived runtime and projected savings. Routine CI has eleven jobs (six tests, three quality jobs, planner and aggregate), compared with fifteen previously. Full CI has fifteen. Coverage instrumentation stays on all legs: this reduction changes matrix cadence only, not the coverage contract.
Batch accepted repairs before pushing, wait on the existing run, and avoid re-requesting reviews already pending. Concurrency cancels superseded runs but cannot reclaim runner time already spent. Release-please's version and lockfile updates are separate commits; treat their intermediate failures separately from ordinary review repairs (see issue 442). The PR Documentation job skips release-please--* heads so the known intermediate lock mismatch does not launch a doomed docs build. CI also checks the release PR lockfile in the planner: a stale intermediate head fails the required gate before allocating compatibility runners, while its lockfile follow-up still runs the full matrix. Normal ready PRs still build docs with uv sync --locked; superseded documentation builds are cancelled. Release documentation continues to build with locked dependencies and deploy from the release tag in publish.yml.
Step 3: Merge the release PR¶
When the release PR is merged:
- release-please creates a
vX.Y.Ztag (usingRELEASE_PLEASE_TOKENPAT) - The tag triggers
publish.yml
Step 4: Automatic publishing¶
The publish.yml workflow:
- Builds wheel and sdist with
uv build - Verifies tag version matches
pyproject.toml - Smoke tests wheel contents (size limit, no test files)
- Installs and verifies the wheel in a clean venv
- Publishes to PyPI via OIDC trusted publishing (no API tokens)
- Generates attestations for package provenance
- Deploys documentation to GitHub Pages
Configuration Files¶
| File | Purpose |
|---|---|
release-please-config.json | Release type, changelog sections, extra-files |
.release-please-manifest.json | Current version manifest (see file for current value) |
.github/workflows/release-please.yml | PR creation + lockfile update |
.github/workflows/publish.yml | Build, verify, publish, docs deploy |
Prerelease Versions¶
Prerelease versions (e.g., 2.0.0a1, 2.0.0rc1) are not currently automated via release-please. If needed, use the manual process below.
Emergency Manual Release¶
If release-please is broken or you need an out-of-band release:
# 1. Bump version in pyproject.toml
uv version 2.0.1
# 2. Commit and merge to main
git add pyproject.toml
git commit -m "chore: bump version to 2.0.1"
# (merge via PR)
# 3. Create and push tag
git checkout main && git pull
git tag v2.0.1
git push origin v2.0.1
The v* tag triggers publish.yml regardless of how it was created.
Troubleshooting¶
Release PR not appearing¶
- Check that commits use conventional prefixes (
feat:,fix:) - Verify
RELEASE_PLEASE_TOKENsecret is set (PAT withreposcope) - Check the release-please workflow run in Actions for errors
Tag not triggering publish¶
- Tags created by
GITHUB_TOKENdo NOT trigger other workflows - The
RELEASE_PLEASE_TOKENPAT is required for cross-workflow triggering - Verify the tag matches the
v*pattern
"Version mismatch" error in publish¶
The tag version must match pyproject.toml. release-please handles this automatically; manual tags require a matching version bump first.
"OIDC token request failed"¶
Ensure Trusted Publishing is configured at pypi.org/manage/account/publishing:
- Workflow:
.github/workflows/publish.yml - Repository:
Alberto-Codes/gepa-adk - Environment:
pypi
"Package already exists"¶
PyPI does not allow overwriting published versions. Bump the version and release again.
Version Numbering¶
Follows Semantic Versioning:
- MAJOR (2.0.0): Incompatible API changes
- MINOR (2.1.0): New features, backwards-compatible
- PATCH (2.0.1): Bug fixes, backwards-compatible