

Versioning in Go workspaces: Changesets
11th January 2026Go workspaces (go.work) solve “how do I build multiple modules in one repo?”, but they don’t give you a versioning/release story beyond “do whatever you need to do”.
- How do service a and service b go to production?
- What about pre-releases / staging?
- What actually triggers a release?
I assume that most teams end up with individual, ad-hoc conventions, which work until they have enough packages and enough releases that the conventions become a queue.
github.com/jakoblorz/go-changesets is a small Go port of the changesets workflow from the JS ecosystem, aimed at making “what should be released” an explicit, reviewable artifact that exists before release time. The tool is built as boring and low-tech as possible. It doesn’t do much, but what it does should work out of the box; hopefully - it does so for me at least :)
CI workflows are super specific to any repository / organization, so this tool doesn’t force you to change it too much. Have a look at .kitchensink/.github/workflows/changeset.yml, the CI isn’t “magic” in any way. Right now, there is a GitHub client to facilitate the creation of releases built in, but you likely want to create the versioning PRs from a custom script in the workflow yourself.
What are changesets?
A changeset is a small markdown file in .changeset/ with YAML frontmatter that encodes “package → bump type” and a human message. The message is the seed for the eventual changelog entry and (optionally) release notes.
---
auth: minor
---
Add OAuth2 support
---
auth: minor
---
Add OAuth2 support
The operational model is intentionally boring. Developers add changesets alongside code changes. CI or a release operator later applies changesets for a specific package: it reads all changesets, filters those affecting the package, selects the highest bump (major > minor > patch), writes the new version, appends a new entry to CHANGELOG.md, and removes the consumed changesets for that package. Publishing then compares the version file to the latest published tag and, when the version is newer, creates a git tag and a GitHub release.
There is also a “run per package” primitive meant for CI fan-out. It builds a package context for each package (current version, latest tag, whether changesets exist, and a changelog preview) and invokes an arbitrary command with that context.
How it maps to Go (and how it deviates)
The JS version assumes package.json as the version source of truth. Go has neither a canonical per-package version file nor a monorepo versioning standard, so this port introduces an explicit version source for Go packages: version.txt in the package root, plus a per-package CHANGELOG.md. Published versions are represented as git tags of the form {package}@v{version} rather than a global v{version}.
The deviations are mostly about keeping the unit of release explicit. Versioning is per-package by default and there is no “single monorepo version” mode. When multiple packages are selected while creating changesets, the tool writes one changeset file per package instead of a single multi-package changeset; this keeps version application independent while still allowing the changes to be treated as related. “Related” is intentionally defined as “created in the same commit”, and a tree view groups changesets by their introducing commit to make coupling visible without trying to infer a dependency graph. Changelog formatting is customizable via a .changeset/changelog.tmpl template. packages can also be disabled by setting version.txt to false, which removes them from discovery and automation.
Changeset groups (coordination, not dependency solving)
In practice, the hard part of monorepo releases is not bumping versions, but coordinating changes that span multiple packages. And doing so without inventing a fragile dependency solver. The grouping mechanism here is intentionally low-tech. If a feature touches three packages, you will usually create three changesets in one commit (or they will be part of a single commit via squash merging). That commit becomes the “group”. The tooling then makes it obvious that these versioning changes are coupled, so you can merge or publish them together if that matters (it is not enforced in any way).
This most likely means that you want to do squash-merging on PRs, to ensure that changesets of the same feature receive the same commit hash.
Snapshotting / RC releases
Snapshotting creates release candidates without modifying version files or consuming changesets. It computes the next version from the pending changesets (in-memory), finds the next -rcN number for that base version on the current branch, tags {package}@v{version}-rc{N}, and creates a GitHub pre-release. This is meant for canary branches and “test before final tag” workflows.
Current state
This is usable today for a Go workspace with multiple packages, but it is still early-stage. If you wire it into CI, pin the tool version and treat changelog templating as code (because it is). I can imagine adding other languages as well (there once was a variant where package.json was also supported), or adding GitLab, Gitea et. al. support.