Skip to content

release: prepare v2026.5.3 (cookie-import + three-mode qa-runner) #3

release: prepare v2026.5.3 (cookie-import + three-mode qa-runner)

release: prepare v2026.5.3 (cookie-import + three-mode qa-runner) #3

Workflow file for this run

name: Release
# Tag-triggered release pipeline.
#
# CI (lint, typecheck, test, audit) runs on Gitea — see .gitea/workflows/ci.yml.
# This workflow exists for two reasons:
# 1. To publish the sdist + wheel as a GitHub release at the tag (always on).
# 2. To publish those same artefacts to PyPI via OIDC trusted-publisher
# (gated on the `ENABLE_PYPI_PUBLISH` repo variable being `'true'` —
# requires the one-time PyPI/TestPyPI setup from #12 to be complete).
#
# Phase 1 (current, until #12 is closed): `publish` (PyPI) is skipped via the
# repo variable gate; the GitHub release ships from `build` directly. Tag
# pushes produce a complete release on
# https://github.com/jmagly/carbonyl-agent/releases with the wheel + sdist
# attached, but nothing is sent to PyPI.
#
# Phase 2 (after #12 is closed): set repository variable
# `ENABLE_PYPI_PUBLISH=true` in Settings → Secrets and variables → Actions →
# Variables. The next tag push will run the publish job; the GitHub release
# then runs after publish as the strict release gate.
#
# Refs: roctinam/carbonyl-agent#11, #12
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Verify tag matches pyproject.toml version
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match pyproject.toml version $PYPROJECT_VERSION"
exit 1
fi
- run: pip install hatch twine
- run: hatch build
- run: twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
publish:
# PyPI publish — gated on ENABLE_PYPI_PUBLISH repo variable.
# Skipped in Phase 1 (pre-#12 completion). To enable: Settings →
# Secrets and variables → Actions → Variables → add `ENABLE_PYPI_PUBLISH`
# with value `true`. Then close #12.
if: vars.ENABLE_PYPI_PUBLISH == 'true'
runs-on: ubuntu-latest
needs: [build]
environment:
name: release
url: https://pypi.org/p/carbonyl-agent
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI via trusted publisher (OIDC)
uses: pypa/gh-action-pypi-publish@release/v1
github-release:
# GitHub release — always runs. Depends on `build` directly so the
# release ships even when the PyPI publish job is gated off (Phase 1).
# When PyPI publish is enabled (Phase 2), `needs: [build, publish]`
# would be the strict ordering; for now we keep `needs: [build]` and
# rely on the gate. If publish runs and fails, github-release still
# runs — that is intentional during Phase 1 so a transient PyPI hiccup
# never blocks the GitHub release (which is the source of truth
# pre-#12). Reconsider after #12 closes.
runs-on: ubuntu-latest
needs: [build]
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Extract release notes from CHANGELOG.md
id: notes
run: |
VERSION="${GITHUB_REF_NAME#v}"
# Pull the section between "## [VERSION]" (or "## VERSION") and the next "## " heading
awk -v ver="$VERSION" '
$0 ~ "^## \\[?"ver"\\]?" { in_section=1; next }
in_section && /^## / { exit }
in_section { print }
' CHANGELOG.md > release-notes.md
if [ ! -s release-notes.md ]; then
echo "No CHANGELOG section for $VERSION; using tag annotation instead." > release-notes.md
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: carbonyl-agent ${{ steps.notes.outputs.version }}
body_path: release-notes.md
files: dist/*
fail_on_unmatched_files: true