Skip to content

Set up the version gate in GitHub Actions

Make a pull request fail when a segment's version is behind what its commits warrant, so a change cannot reach the default branch without shipping.

Time: about ten minutes. You need: Admin on the repository.


Why a gate and not an automatic bump

The obvious design is to let CI compute the bump, commit it and push it back. GitHub stops that at the last step, and says so plainly:

With the exception of workflow_dispatch and repository_dispatch, other GITHUB_TOKEN-triggered events do not create workflow runs at all.

Events that trigger workflows

That is deliberate: it is what stops a workflow that pushes re-triggering itself forever. But it means a bump written by CI arrives as a head commit with no checks of its own, so a branch protection rule requiring status checks blocks the pull request with nothing able to clear it.

You can work around it with a Personal Access Token or a GitHub App, at the cost of a long-lived credential with write access. skillup does not, because the alternative is one command run occasionally.

GitLab's CI_JOB_TOKEN behaves the same way, for the same reason. See Set up the version gate in GitLab CI.


1. Protect the default branch

The gate only means something if nothing can bypass it.

Settings → Branches → Add branch ruleset for main:

Rule Value
Restrict deletions on
Require a pull request before merging on
Require status checks to pass on, with version-check selected
Block force pushes on

The required-status-check entry only appears in the list after the workflow has run at least once, so add the workflow first and come back to it.

2. Add the workflow

.github/workflows/version-check.yml:

name: version

on:
  pull_request:

jobs:
  version-check:
    runs-on: ubuntu-latest
    permissions:
      contents: read          # the gate only reads; it never pushes
    steps:
      - uses: actions/checkout@v4
        with:
          # skillup reads each manifest's whole history. The default checkout is
          # shallow, and a shallow clone yields a confidently wrong answer
          # rather than an error.
          fetch-depth: 0

      - uses: actions/setup-go@v5
        with:
          go-version: stable

      - name: Install skillup
        # Pinned, not @latest: this gate decides whether a change ships, so it
        # should not change underneath the repository without a commit saying so.
        run: go install gitlab.com/phpboyscout/skillup/cmd/skillup@v0.1.0

      - name: Check segment versions
        run: skillup check --path .

Three details that matter:

  • fetch-depth: 0. Without it the checkout is shallow, skillup sees a truncated history, and computes a version that is wrong without failing.
  • contents: read. The gate never writes, so do not grant more. If you later add a job that does push, give that job contents: write rather than widening this one.
  • Pin the version; do not track @latest. This job decides whether a change reaches consumers. On @latest that decision changes without a commit saying so, and the change lands on whichever pull request happens to run next.

go-version: stable is enough today — skillup needs Go 1.26.5 or newer.

Keep the pin current

Nothing bumps this line by default. Dependabot's gomod ecosystem reads go.mod, and its github-actions ecosystem reads uses: — neither looks at a run: command, and skillup is not on GitHub anyway. Renovate can, with a custom manager in renovate.json:

{
  "customManagers": [
    {
      "customType": "regex",
      "managerFilePatterns": ["/^\\.github/workflows/.+\\.ya?ml$/"],
      "matchStrings": [
        "go install gitlab\\.com/phpboyscout/skillup/cmd/skillup@(?<currentValue>v[\\d.]+)"
      ],
      "datasourceTemplate": "gitlab-tags",
      "depNameTemplate": "phpboyscout/skillup"
    }
  ]
}

Otherwise the pin stays where you put it — safe, but frozen. Watch the releases page and bump it by hand.

3. Check it bites

On a branch, change something inside a segment and commit it as a feat: or fix: without touching the version. The check should fail with:

  my-segment    is 0.3.0 but its commits warrant 0.4.0 (minor)
      a1b2c3d4  feat(my-segment): add a thing (minor)

  To fix, on your branch:

      skillup apply --path .
      git add -u && git commit --amend --no-edit   # or a new commit

Run those two commands, push, and it goes green.


Troubleshooting

The check passes but should not. Confirm fetch-depth: 0. A shallow checkout is the usual cause of a wrong-but-confident answer.

version-check is not offered as a required status check. It has to have run once before GitHub will list it. Open a throwaway pull request, let it run, then add the rule.

Every segment wants to go to 0.1.0. skillup is reading a repository whose manifests have no history, so every segment looks new. Confirm --path points at the right root.

The check does not run on a fork's pull request. Workflows from forks get a read-only token by default. That is fine here — the gate only reads — but if you add anything that writes, it will fail for external contributors.