11th January 2026
Go 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 your org does”. I assume that most teams end up with individual, ad-hoc conventions, which work until they have enough projects 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…
11th January 2026
Go 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 your org does”. I assume that most teams end up with individual, ad-hoc conventions, which work until they have enough projects 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 project, 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 need to create the versioning PRs from a custom script in the workflow still.
What are changesets?
A changeset is a small markdown file in .changeset/ with YAML frontmatter that encodes “project → bump type” and a human message. The message is the seed for the eventual changelog entry and (optionally) release notes.
The operational model is intentionally boring. Developers add changesets alongside code changes. CI or a release operator later applies changesets for a specific project: it reads all changesets, filters those affecting the project, 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 project. 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 project” primitive meant for CI fan-out. It builds a project context for each project (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-project version file nor a monorepo versioning standard, so this port introduces an explicit version source for Go projects: version.txt in the project root, plus a per-project CHANGELOG.md. Published versions are represented as git tags of the form {project}@v{version} rather than a global v{version}.
The deviations are mostly about keeping the unit of release explicit. Versioning is per-project by default and there is no “single monorepo version” mode. When multiple projects are selected while creating changesets, the tool writes one changeset file per project instead of a single multi-project 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. Projects 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 projects. And doing so without inventing a fragile dependency solver. The grouping mechanism here is intentionally low-tech. If a feature touches three projects, 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 {project}@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 projects, 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.