Automatic versioning for GitHub Actions authors

As a maintainer of 3 GitHub Actions, I've always struggled a bit with the versioning of them. GitHub's documentation provides guidance, but no easy way to verify you did everything right. Based on a quick scan of the actions on the marketplace I'm not alone, even GitHub's own actions have some.

Automatic versioning for GitHub Actions authors
Photo by Zak G / Unsplash

As a maintainer of 3 GitHub Actions, I've always struggled a bit with the versioning of them. GitHub's documentation provides guidance, but no easy way to verify you did everything right. Based on a quick scan of the actions on the marketplace I'm not alone, even some of GitHub's own actions have some inconsistencies when it comes to it.

This is why I cobbled together v1 of my Actions Semver Checker action a couple of years ago and it has served me well. But as I threw the action together in a short amount of time way back when, it contained a few bugs, was very hard to maintain and I always dreaded making changes to it, since I had fixed my own repos, so it was hard to test whether I broke anything or accidentally introduced unwanted side effects.

This is why I had Copilot Agent use the existing v1 implementation as a blueprint to build a new version. But with a lot of improvements. I gave Copilot the order to refactor the action with the following constraints:

  • Make sure the old implementation and the new one can be called side-by-side so it's easy to compare the differences.
  • Extract the validation rules into separate scripts so they're easier to maintain and test.
  • Provide manual remediation options wherever possible.
  • Implement an autofix functionality which tries to execute the manual remediation steps.
  • Add unit tests for everything.
  • Add integration tests for everything.
  • Try to remain backwards compatible with v1 as much as possible when it comes to the inputs.
  • I gave it links to all the relevant GitHub docs and blogs for:
    • GitHub CLI release commands
    • Releases REST API
    • Tags REST API
    • GitHub's GraphQL API
    • Blogs discussing immutable releases
    • GitHub Actions versioning

I used Claude Opus to generate a plan for this, since it's a pretty big effort. I went over the plan and made a few tweaks and then had the Copilot Coding Agent spend a good 45 minutes to do the work. And this resulted in my first vibe coded GitHub Action. And the result? It did mostly what it needed to do. It surfaced a couple of gaps in the initial plan and the request to keep both old and new code side-by-side caused some interesting interweaving between the 2 implementations.

But now that I had a test suite of 90+ tests, I felt much better changing the actual behavior. And after many round trips with the Copilot Coding Agent as well as a few hands-on sessions inside Visual Studio Code with Copilot, I am proud to release v2 of the GitHub Actions Semver Checker Action.

GitHub - jessehouwing/actions-semver-checker: This action checks the version tags in your repository to ensure correct semantic versioning behavior.
This action checks the version tags in your repository to ensure correct semantic versioning behavior. - jessehouwing/actions-semver-checker

What does it do exactly?

Good question, the action is meant to be installed into the repository which hosts a GitHub action, like actions/checkout or jessehouwing/actions-semver-checker. It analyzes all branches, tags and releases and validates them based on a number of rules:

  • There should be a vX tag or branch (configurable) which points to the latest vX.y.z tag.
  • There should be a vX.Y tag or branch (configurable) which points to the latest vX.Y.z tag.
  • There should be a vx.y.z tag
  • There should be no vx.y.z branch
  • new - There should be a release for every vx.y.z tag
    • new - That release should be immutable
    • new - The release for the highest vx.y.z should be set to "latest"
  • new - There should be no release for vX and vX.Y tags
  • new - The action should be published to the marketplace
    • new - The action.yml should have the correct metadata elements
    • new - The latest version of the action should be published to the marketplace
  • new - Immutable releases should be turned on

And for almost all of these validations, I managed to build an autofix option which automatically corrects any issues found.

What's new

As you can see in the list above, v2 adds a lot of new validations and it also adds the autofix feature to automatically resolve most of the issues found. If the action is not able to fix the issue, manual remediation steps are logged to the workflows summary pages.

Autofixes available:

  • delete/update/create branches
  • delete/update/create tags
  • convert branch to tag
  • convert tag to branch
  • delete/update/create/publish releases
  • automatically set the latest version
  • republish releases to make them immutable

There are a few other improvements worth noting:

  • Fully configurable which suites of rules you want to run
  • You no longer need to check out the whole repo to run the action
  • Retry logic and handling of rate limits
  • Available as module on the PowerShell Gallery

There is one unfortunate change to v1 I could not work around

  • You should pass the ${{ secrets.GUTHUB_TOKEN }} to the token: input of the action
  • For some validations the token must have contents: write permissions (to read draft releases)
  • For autofix functionality the token must have contents: write permissions
  • To fix certain issues, the token needs permissions which cannot be granted to GitHub Actions, such as workflows: write. Use a GitHub App or Fine-grained Personal Access Token instead.

I recommend you install the action into your action repository with its default settings, this will analyze your repo and will detect all rule violations.

Change the settings of the action to match your desired behavior:

  • floating-versions-use can be set to tags or branches depending on how you want to manage the vx and vx.y versions.
  • check-minor-version can be set to none, warning or error to validate the existence of vx.y versions.
  • check-releases can be set to none, warning or error to validate the existence of releases for vx.y.z tags.
  • check-release-immutability can be set to none, warning or error to validate whether releases are immutable.
  • ignore-preview-releases can be set to true or false to exclude preview releases when checking floating versions (vx an vx.y).
  • ignore-versions can exclude versions from validation completely. This can be used to ignore really old versions or versions that were accidentally made immutable and can no longer be fixed.
name: Check SemVer

on:
  push:
    branches: ['main']
    tags:
      - '*'
  workflow_dispatch:

permissions: {}

jobs:
  check-semver:
    permissions:
      contents: write

    concurrency:
      group: '${{ github.workflow }}'
      cancel-in-progress: true

    runs-on: ubuntu-latest

    steps:
      - uses: jessehouwing/actions-semver-checker@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          check-release-immutability: none
⚠️ be extra careful with the check-release-immutability setting prior to turning on autofix. Most automatic fixes can be easily undone using the git commandline, but once a release is made immutable, there is no way back.

Once your existing tags have been cleaned up and you understand the implications, turn on release immutability checks:

    steps:
      - uses: jessehouwing/actions-semver-checker@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          check-release-immutability: error

Use the action in read-only mode for a while, manually performing the suggested remediation steps.

When you're confident the action does what you want it to do, consider turning on autofix: true.

    steps:
      - uses: jessehouwing/actions-semver-checker@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          autofix: true

With autofix turned on, the only thing you need to do to release a new version and update all tags and releases, is to push a new tag to to create a new release with the correct name: vx.y.z. This will trigger autofix to do the rest on your behalf.