July 05, 2026

Counting Builds with Git Tags

I’ve been writing here about leadership, technology, and mobile engineering for years, and somehow I’ve never written about my most popular open source project. It’s Build Tag Number, a GitHub Action that counts.

The Problem

For much of my career, I’ve built mobile apps and led mobile organizations. Mobile apps have a rule: build numbers only go up. Google Play rejects an app bundle whose versionCode isn’t higher than the previous one. TestFlight is just as strict.

GitHub gives you GITHUB_RUN_NUMBER out of the box which can be used for build numbers, and for plenty of teams that’s fine. But it’s scoped to a single workflow. Split your CI into separate workflows for PRs and releases, and now you have two counters that disagree with each other. And you can’t seed it. If you’re migrating from something like Jenkins and your last build was 4,873, GitHub cheerfully starts you back at 1.

I wanted one counter for the whole repo that I could start anywhere.

Usage

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Generate build number
      uses: onyxmueller/build-tag-number@v1
      with:
        token: ${{ secrets.github_token }}
    - name: Print new build number
      run: echo "Build number is $BUILD_NUMBER"

Every later step gets a BUILD_NUMBER environment variable. There’s also a step output, steps.buildnumber.outputs.build_number, if you wanna pass it to other jobs. I feed it to Gradle as the versionCode.

The trick: your repo is already a database

The counter is a git tag. When the action runs it finds the ref build-number-42, creates build-number-43 on the current commit, and deletes the old one.

Here’s why I like this design:

  • No external service. Your repo already stores refs. It can store one more!
  • No extra commits. The tag rides along without touching your history.
  • It’s seedable. Moving from another CI system? Push git tag build-number-4873 and the next build is 4874.
  • It’s fast. The whole action is one JavaScript file with zero npm dependencies. It calls the GitHub API with Node’s built-in https module. A build-number generator shouldn’t spend thirty seconds installing packages to add one to a number.

Multiple Counters

Got a client and a server in one repo? The prefix input gives each its own counter:

    - uses: onyxmueller/build-tag-number@v1
      with:
        token: ${{ secrets.github_token }}
        prefix: client

That creates tags like client-build-number-12, independent from server-build-number-31. And if you want to keep every old tag as a paper trail, set delete_previous_tag: false.

Why I Do This

Build Tag Number is not impressive engineering. It adds one to a number! But it runs in pipelines I’ll never see, for developers I’ll never meet, and every so often one of them shows up in my issue tracker to tell me how they’re using it. Some send pull requests that make it better for everyone.

I’ve spent twenty-plus years building software for big brands. Watching a tiny tool I shared quietly help strangers still beats most of it. That feeling is why I do open source.

You probably have a small utility that scratches your own itch. I encourage you to share it.

And if your builds need numbers that only go up, check this out.

— Onyx