Implement changelog-driven release workflow - #6822
Conversation
A package is now published exactly when the newest version heading in its CHANGELOG.md has no corresponding git tag, and tags are only pushed after a successful publish — a failed build/publish is retried by pushing a fix on top of the changelog bump, with no tag or release cleanup. - scripts/release.py: shared helper (detect / plan / materialize / prepare-publish / extract-notes) with changelog parsing, version planning, towncrier materialization, and alpha-section collapsing. - release_from_changelog.yml (new): on push to main and r/**, publish any changelog version without a tag. Finals only publish from main or r/hotfix/**. - publish.yml: now takes (package, version) via workflow_call/dispatch; validates against the changelog and branch rules, tags the local checkout, builds, publishes, then pushes the tag and creates the GitHub release with notes extracted from the changelog. Same filename and pypi environment, so PyPI trusted publishing is unchanged. - dispatch_release.yml: same action modes, but now materializes changelogs. Prerelease actions push alphas straight to an r/pre-<date> branch (no approval); release-* actions open a PR whose review+merge is the release approval. release-from-prerelease collapses alpha sections into the single final version section, so alphas never appear in a final changelog. The dispatch-release-approval environment gate is retired. - auto_release_internal.yml: internal packages publish through publish.yml with an auto patch-bump version computed inside the per-package publish lock; no more tag-before-publish. - Obsolete per-workflow scripts removed; CONTRIBUTING.md documents the flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RytVevvLUNztmc2Shr9fgq
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Greptile SummaryImplements a changelog-driven release workflow.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported subpackage artifact-path issue is invalid because workspace builds place distributions in the repository-root dist directory consumed by the workflow.
|
| Filename | Overview |
|---|---|
| scripts/release.py | Adds the unified release planner and changelog-based publishing utilities; no eligible follow-up issue was identified. |
| .github/workflows/publish.yml | Replaces tag-triggered publishing with validated build, gated upload, and post-publish tagging stages; the previously reported workspace artifact-path concern is invalid. |
| .github/workflows/release_from_changelog.yml | Adds detection and publication orchestration for untagged changelog versions. |
| .github/workflows/dispatch_release.yml | Materializes release versions and routes prereleases to release branches or final releases through pull requests. |
| tests/units/test_release.py | Adds coverage for release planning, changelog processing, validation, and package discovery. |
Reviews (4): Last reviewed commit: "Fix approval-gate token scope and stale ..." | Re-trigger Greptile
Merging this PR will degrade performance by 3.36%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
… fail-closed guards Restructure publish.yml into three stages so every PyPI upload requires human approval while builds stay automatic: - build (unprivileged): validate against changelog/branch rules/lockstep, tag locally, build, verify artifact core-metadata versions, extract release notes, upload everything as a workflow artifact. - publish: gated by the pypi environment's required reviewers — alphas and internal packages included. Holds the only id-token privilege, runs no repository scripts and resolves no dependencies (only uv publish on the pre-built artifact), and fails closed if it cannot prove the pypi environment actually requires reviewers. - tag-and-release: pushes the tag and creates the GitHub release only after a successful upload. Other hardening: - Branch policy: prereleases may only publish from r/pre-* or r/hotfix/** (no longer any r/** or main); push triggers narrowed to match. - reflex/reflex-base lockstep enforced fail-closed at detect and publish time, and reflex publishes in a second phase only after the rest of the batch succeeds; a report job turns partial releases into one loud failure. - Every publish entry point now shares the publish-<package> concurrency group, closing the dispatch-vs-automatic double-publish race. - changelog CI rejects hand-added version headings (they are the publish trigger); release/* branches and the skip-changelog label are exempt. - GitHub release "Latest" is only set when the version is actually the newest final (hotfixes of old lines no longer steal the badge). - scripts/release.py grows pin-reflex-base (replaces the sed script, asserts exactly one rewrite) and verify-dist (compares each artifact's METADATA/PKG-INFO Version, replacing the filename heuristic); dates are UTC; new-prerelease-* refuses to silently abandon an in-progress alpha train; non-alpha prereleases get a clear error. - Script dependencies are locked with hashes (scripts/release.py.lock, used automatically by uv run --script). - Git pushes authenticate via gh's credential helper instead of a token in the remote URL, and materialization commits stage only changelogs and news fragments with an explicit empty-diff guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RytVevvLUNztmc2Shr9fgq
The pypi-gate job's permissions block implicitly set contents: none, which could 403 the environments API read and permanently block publishing with a misleading error. Grant contents: read and give the two failure modes distinct messages (token/configuration problem vs missing reviewers). Also update the comments and docs that still described the pre-approval iteration: the dispatch workflow header and CONTRIBUTING no longer claim alphas publish without approval or that PR review alone approves a release — every upload waits for pypi environment approval; PR merge is how final versions land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RytVevvLUNztmc2Shr9fgq
…ecksums, strict batch report - release.py's header now pins exact versions matching release.py.lock, and every workflow call site runs uv with --no-config --locked so any header/lock drift fails loudly instead of silently re-resolving (--no-config keeps the moving [tool.uv] exclude-newer window in the repo's pyproject from invalidating the script lock daily). - The changelog heading guard no longer re-implements the parser in grep: a new check-headings subcommand diffs version headings against the PR base using the exact parser the release pipeline publishes from, closing the whitespace/case bypasses. Its escape hatch is a dedicated changelog-version-edit label — skip-changelog only waives the fragment check and no longer disables the publish-trigger guard. - The build job writes a SHA256SUMS manifest covering dist/* and the release notes; the gated publish job verifies it with coreutils after the approval pause, so what the human approved is byte-identical to what uploads. - The release batch report treats any leg result other than success/skipped (cancelled, timed_out, rejected approval) as a failure instead of reporting green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RytVevvLUNztmc2Shr9fgq
Type of change
Description
This PR implements a comprehensive changelog-driven release system that makes
CHANGELOG.mdthe source of truth for publishing packages. The system replaces the previous tag-based release workflow with a more robust approach that handles failed publishes gracefully and supports both final and prerelease versions.Key Changes
New Release Script (
scripts/release.py)detect: Identifies packages with untagged changelog versionsplan: Computes next versions based on release action (new-prerelease-, continued-prerelease, release-, etc.)materialize: Writes planned versions to changelogs using towncrierprepare-publish: Validates (package, version) pairs against changelog, branch rules, and tagsextract-notes: Exports changelog sections for GitHub release notescollapse_prereleases()to merge accumulated alpha sections into final releasesNew Workflows
release_from_changelog.yml: Publishes any changelog version lacking a git tag on pushes to main/r/** branchesdispatch_release.yml: Materializes news fragments into changelogs at next versionrelease-from-prereleasecollapses alpha sections into final versionpublish.yml: Accepts package/version inputs, creates tags only after successful PyPI uploadauto_release_internal.yml: Uses new publish workflow for internal packagesSupporting Scripts
open_release_pr.sh: Opens PR with changelog changes for release actionspush_prerelease.sh: Pushes prerelease versions to r/pre-* branchescreate_release.sh: Creates GitHub releases with changelog notespush_tag.sh: Pushes git tags after successful publishverify_dist_version.sh: Validates built artifacts carry correct versionDocumentation
CONTRIBUTING.mdwith changelog-driven release process for maintainersDesign Rationale
Testing
tests/units/test_release.pycovering:All tests pass with adequate coverage.
Checklist
uv run ruff check .anduv run ruff format .passuv run pyright reflex testspasseshttps://claude.ai/code/session_01RytVevvLUNztmc2Shr9fgq