Skip to content

Release Process

github-actions[bot] edited this page Nov 2, 2026 · 2 revisions

Release Process

Releases are automated by release-please. Nobody edits a version number or the changelog by hand, and nobody pushes tags.

Versioning

  • Semantic Versioning, with tags vX.Y.Z. Before 1.0, a breaking change bumps the minor version and a feature bumps the patch.
  • The version is derived from Conventional Commit messages on main. PRs are squash-merged, so it's the PR title that counts:
PR title Bump CHANGELOG section
feat: ... minor (patch before 1.0) ✨ Features
fix: ... patch 🐛 Bug Fixes
perf: ... patch ⚡ Performance
feat!: ..., or BREAKING CHANGE: in the body major (minor before 1.0) ⚠ Breaking changes
refactor:, docs:, revert: patch their own sections
build:, ci:, test:, chore: none on their own hidden
  • The PR title check enforces the format. A wrong type means a wrong version, so reviewers check it.
  • The version lives in pyproject.toml and .release-please-manifest.json; interpose.__version__ reads the installed metadata. CI fails if the two files disagree.

How a release happens

  1. Merge PRs to main as usual.
  2. On every push to main, release-please updates a single open Release PR (chore: release X.Y.Z). It bumps the version, writes the new CHANGELOG.md entry from the commit titles. A follow-up job refreshes uv.lock on the same PR.
  3. Review the Release PR like any other: is the version right, and does the changelog read well? Fix a wording problem by editing the PR description under the version heading (release-please keeps that), or fix the original commit's PR title and rerun the Release workflow.
  4. The Release PR runs the wiki check in strict mode: any leftover <!-- guidance --> skeleton text in docs/wiki/ blocks the release.
  5. Merge it. release-please tags vX.Y.Z and publishes the GitHub Release with the changelog entry.

Pre-releases and hotfixes

  • Pre-release: run the Release workflow after adding "prerelease": true and "prerelease-type": "rc" for the package in release-please-config.json, in a PR. Remove them again for the final release.
  • First release is 0.1.0, and feat: stays a patch bump until 1.0. Leave 0.x with the force below.
  • Force a version: a commit with Release-As: 1.0.0 in its body (an empty chore: commit is fine) makes the next Release PR use that version.
  • Hotfix: a fix: PR to main and a release as usual. There are no long-lived release branches.

Dependencies pinned to a git tag

Dependabot can't update a dependency pinned to a GitHub tag, such as "NAME @ git+https://github.com/OWNER/REPO@v1.2.3": it looks the bare name up on PyPI instead. The weekly Tag check workflow (python-tag-check.yml, Monday 05:00 UTC) does it: for each such pin, in [project] dependencies, optional dependencies, dependency groups or a [tool.uv.sources] tag =, it finds the repository's highest vX.Y.Z tag and opens one PR, chore(deps): bump NAME from vOLD to vNEW on tag-check/NAME, with the pin's line rewritten and uv lock --upgrade-package NAME. A major bump, or a minor one below 1.0, adds "(breaking)" to the title. Branch, commit and pre-release pins are listed in the job summary and never bumped. scripts/tag-check.py --dry-run shows what it would do.

  • Treat a Dependabot PR for such a name as suspect: it proposes whatever PyPI has under that name, which may be someone else's package. Close it.
  • Without the release App, CI doesn't run on these PRs: close and reopen one to run it. A private dependency needs the App, installed on that repository.

Setup note for maintainers: Release PRs opened with the default GITHUB_TOKEN don't trigger CI. The organisation's release GitHub App (RELEASE_APP_CLIENT_ID variable, RELEASE_APP_PRIVATE_KEY secret) fixes that; see template-python's README.

interpose

GitHub • Releases


📚 Getting Started

📖 Core Concepts

🔒 Security

🆘 Help & Support

🧑‍💻 For Developers


🔑 Key Features

  • 🧅 One middleware loop; fail open or closed is policy, per project and per middleware
  • 🥞 One lookup order: _FILE > environment > YAML > default
  • 🧱 Strict types: no silent 0 for a YAML 0.5
  • 🧭 Where each value came from, for every setting
  • 🔒 Secret: masked in logs, reprs and errors
  • 📈 Prometheus text with labels from declared value sets

Clone this wiki locally