Skip to content

ci: switch to tag-driven releases via hatch-vcs - #97

Merged
MarTrepodi merged 1 commit into
mainfrom
ci/tag-driven-release
Jun 3, 2026
Merged

ci: switch to tag-driven releases via hatch-vcs#97
MarTrepodi merged 1 commit into
mainfrom
ci/tag-driven-release

Conversation

@MarTrepodi

Copy link
Copy Markdown
Collaborator

Problem

The release workflow has never succeeded. It bumps the version in src/swgoh_comlink/version.py, commits it, and runs git push origin main --follow-tags. Branch protection on main rejects that push:

remote: error: GH006: Protected branch update failed for refs/heads/main.
  - Changes must be made through a pull request.
  - 10 of 10 required status checks are expected.
! [remote rejected] main -> main (protected branch hook declined)

With enforce_admins: true and PRs required, nothing can push a commit directly to main. Worse, --follow-tags succeeds in pushing the tag even when the branch push is rejected, so each failed run leaves an orphaned tag pointing at a bump commit that never landed (this happened today with v2.1.0; I deleted it).

Fix: tag-driven versioning

Derive the version from the Git tag instead of committing it, so the release never touches main.

  • pyproject.toml — use hatch-vcs as the version source; write the resolved value to a git-ignored _version.py via the vcs build hook. Removed the dead [tool.semantic_release] block (it referenced the old version.py mechanism).
  • version.py — read __version__ from the generated _version.py, falling back to importlib.metadata, then a sentinel for unbuilt trees.
  • .gitignore — ignore the generated _version.py.
  • release.yml — rewritten as build → publish → tag:
    1. Compute the next version from the latest tag + a bump input (patch/minor/major).
    2. Build with SETUPTOOLS_SCM_PRETEND_VERSION so no tag is needed yet.
    3. Publish to PyPI via trusted publishing.
    4. Only after a successful publish, create the tag + GitHub Release (gh release create --target <sha> --generate-notes). A failed run therefore never leaves an orphaned tag, and no step pushes to main.
  • CONTRIBUTING.md — documents the new process.

Behavior change

The repo no longer carries a committed version string, and CHANGELOG.md is no longer regenerated into the repo on release — release notes are auto-generated into the GitHub Release body instead. The existing CHANGELOG.md remains as historical record.

Verification (local)

  • hatch version resolves 2.0.8.dev7+g… from existing tags ✅
  • uv sync runs the vcs build hook → generates _version.py; runtime chain (version.py__init__ → client repr) reports the derived version ✅
  • SETUPTOOLS_SCM_PRETEND_VERSION=2.1.0 hatch build produces correctly named swgoh_comlink-2.1.0.tar.gz / …-py3-none-any.whl
  • Full unit suite: 598 passed
  • ruff check src/ clean; workflow YAML parses; job graph is build → pypi-publish → tag-release ✅

After merge

Trigger the release from the Actions tab → "comlink-python release" → choose bump. It will publish to PyPI and tag vX.Y.Z on the main commit.

🤖 Generated with Claude Code

The release workflow pushed a version-bump commit directly to `main`,
which the branch protection rules reject ("Changes must be made through
a pull request", enforce_admins=true). Every release attempt failed at
the push step and left an orphaned tag behind.

Derive the package version from the Git tag instead:

- pyproject: use hatch-vcs as the version source and write the resolved
  value to a git-ignored `_version.py` via the vcs build hook. Drop the
  dead `[tool.semantic_release]` config that referenced the old file.
- version.py: read `__version__` from the generated `_version.py`,
  falling back to installed package metadata, then a sentinel.
- gitignore: ignore the generated `_version.py`.
- release.yml: rewrite as build -> publish -> tag. The version is
  computed from the latest tag plus a `bump` input and forced into the
  build via SETUPTOOLS_SCM_PRETEND_VERSION, so the build needs no tag.
  The Git tag and GitHub Release are created last, only after a
  successful PyPI publish, so a failed run never leaves an orphaned tag.
  No step pushes to `main`, so the flow is compatible with branch
  protection.
- CONTRIBUTING: document the tag-driven release process.

Verified locally: hatch-vcs resolves the version from existing tags,
the vcs build hook generates _version.py on install, the runtime import
chain (version.py -> __init__ -> client repr) reports the derived
version, and a pretend-version build produces correctly named
swgoh_comlink-X.Y.Z artifacts. Full unit suite (598 tests) passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci Changes related to continuous integration processes dependencies Updates to package dependency libraries code labels Jun 3, 2026
@MarTrepodi
MarTrepodi merged commit 28a2a9c into main Jun 3, 2026
15 of 16 checks passed
@MarTrepodi
MarTrepodi deleted the ci/tag-driven-release branch June 3, 2026 18:03
MarTrepodi added a commit that referenced this pull request Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Changes related to continuous integration processes code dependencies Updates to package dependency libraries documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant