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.
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 |
CI, branch or pull request (the | The next patch as a pre-release, for example |
|
|
| The same |
Any of the above, overridden |
|
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.61The 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.3An 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 |
|---|---|
|
|
| Between |
| Just below |
| Just below |
|
|
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.1That 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 --tagsOr 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
- How a release happens, which is where a real version number actually gets decided
- What happens when you open a pull request
- Building PatchMon locally
Was this helpful?
Your feedback shapes what we write next.
