How versioning works

No source file in PatchMon declares a version. The git tag is the only source, injected at build time. How edge builds are versioned as a pre-release of the next patch, and what to do when you see 0.0.0.

M
Written By M. Ibrahim (Admin)Last updated 9 days ago

This is the least obvious part of the PatchMon build, and the part most likely to surprise you. It is worth five minutes before you go looking for a version number to edit.

Nothing in the repository declares a version

If you go looking for the version in the source, you will find this:

// server-source-code/internal/config/config.go
var DefaultVersion = "0.0.0"

// agent-source-code/internal/pkgversion/version.go
var Version = "0.0.0"

That is not a stale value waiting to be bumped. It is a deliberate non-version.

The git tag is the only place a PatchMon version is declared. Every build path works it out from there and injects it at link time with -ldflags -X. There is no version to bump in a pull request, and editing those values by hand is always the wrong fix.

Both were const until recently, which quietly broke things: the Go linker cannot overwrite a constant, so the -X flags the build had been passing for a long time did nothing at all, and every binary reported its hardcoded source value. Making them var is what allows the injection to work.

Where each build path gets the version

Build path

Version comes from

CI, release tag

The tag, with the leading v stripped

CI, branch or pull request (the edge image)

The next patch as a pre-release, for example 2.0.3-rc.61

make build or make run

git describe --tags --abbrev=0, that is, the most recent release

./docker/build.sh

The same git describe, passed to Docker as --build-arg VERSION

Any of the above, overridden

VERSION=2.0.1 make run

A local development build reports the last released version, which is the honest answer: your working tree is unreleased changes sitting on top of that release.

CI is the exception, and it is worth understanding why.

Edge builds are a pre-release of the next patch

Every merge to main publishes the edge image. That image contains code written after the last release, so calling it 2.0.2 understates it. It now reports the next patch version with a pre-release counter:

last tag v2.0.2 + 61 commits  ->  2.0.3-rc.61

The counter is the number of commits since the tag, not a CI run number, so rebuilding the same commit gives the same version. It resets at each release, which is safe because the patch number goes up at the same moment.

The ordering is the point:

2.0.2  <  2.0.3-rc.61  <  2.0.3

An edge instance is therefore ahead of the last release and still behind the next one. When 2.0.3 is published, edge instances correctly see it as an upgrade.

Why not just call it 2.0.3? Because it would collide with the real release. Agents ask the server what version they should be on, and the server answers with the version of the agent binary bundled in its own image. That check is a string comparison. If an edge image called itself 2.0.3 and the 2.0.3 release also called itself 2.0.3, agents on those hosts would compare the two, see no difference, and carry on running mid-cycle binaries forever. The server's own update notice would stay silent for the same reason. A pre-release avoids that entirely.

What this means if you run edge. You are on a beta of the next release, and it is labelled as such in the UI. Your agents follow the server: an edge server hands out edge agents, and when you move to a released image your agents update to match on their next check. Nothing manual is required in either direction.

What 0.0.0 means

If a PatchMon UI shows 0.0.0, the build skipped the version injection. It is a signal, not a default.

That is why the fallback is 0.0.0 rather than something plausible like the last release. A believable-but-wrong number hides the problem; an obviously impossible one does not.

The most common cause is calling docker build directly instead of using ./docker/build.sh. .dockerignore excludes .git, so the Dockerfile cannot work the version out for itself and depends on the caller passing it in.

What counts as a valid version

PatchMon understands MAJOR.MINOR.PATCH and MAJOR.MINOR.PATCH-PRERELEASE. Pre-releases sort below the release they lead up to, which is standard semantic versioning behaviour and is what makes the edge scheme above work.

Anything else still degrades quietly rather than failing, so it is worth knowing what a malformed string actually does:

Version string

Compares as

2.0.3

2.0.3

2.0.3-rc.61

Between 2.0.2 and 2.0.3

2.0.2-60-gabc1234

Just below 2.0.2, which is still not what you want

0.0.0-dev

Just below 0.0.0

dev

0.0.0

The third row is the one to watch. A plain git describe returns something like v2.0.2-60-gabc1234. That is read as a pre-release of 2.0.2, so an instance built that way believes it is behind the current release and offers an update that will never satisfy it. This is why local build paths use --abbrev=0 to get the bare nearest tag, and why docker/build.sh rejects anything that is not three plain numbers.

Only CI produces a pre-release version, and only from a deliberate calculation. If you are adding a new build path, produce a bare MAJOR.MINOR.PATCH and apply the same guard.

Testing the auto-update path

Because the version is a build input rather than a source constant, you can deliberately build an instance that believes it is out of date. Build a version below the current release:

VERSION=2.0.1 make run
./docker/build.sh --version 2.0.1

That instance will see an update as available and exercise the update flow. This is easier and more reliable than editing source and remembering to revert it.

Common questions

My build fails saying it cannot determine a version.

git describe walks back through history looking for the nearest tag, so it needs both the tags and the commits between you and them. A shallow clone (git clone --depth 1) has the commits missing, and fetching tags on its own does not fix that, because the tags then point at commits you do not have. Unshallow it:

git fetch --unshallow --tags

Or skip the problem entirely and pass the version explicitly with --version or VERSION=.

Should my pull request bump a version anywhere?

No. There is nothing to bump. Versions are decided when a release is tagged, which is a maintainer action. The edge pre-release number is calculated by CI from commit history, so that is not something you set either.

The root package.json says 0.0.0. Is that a mistake?

No. Both package.json files are private and nothing reads their version at runtime. They are pinned to 0.0.0 so there is no second number to keep in step.

Where to go next

Was this helpful?

Your feedback shapes what we write next.