skyl is four Go modules in one repository, and releasing them has an ordering constraint that is easy to get wrong — and permanent when you do, because tags on the module proxy are immutable.
The modules and their tags#
| Module | Import path | Tag |
|---|---|---|
| library | github.com/BAGOMBEKA-JOB-DEV/skyl | v0.1.0 |
| Anthropic adapter | …/skyl/provider/anthropic | provider/anthropic/v0.1.0 |
| OpenTelemetry | …/skyl/otel | otel/v0.1.0 |
| gateway | …/skyl/gateway | gateway/v0.1.0 |
Run the script#
scripts/release.sh vX.Y.ZIt performs every go.mod rewrite and stops between modules so you can confirm
each tag landed. It never tags and never pushes — those remain deliberate
human actions.
Why a subdirectory module needs a prefixed tag#
Go finds a module in a subdirectory by looking for a tag whose prefix is that
subdirectory. provider/anthropic/v0.1.0 publishes the adapter; a bare
v0.1.0 publishes only the root module.
Tagging one does not tag the other, and the version numbers do not have to move together — though keeping them aligned is far easier to reason about.
Why replace must go#
Deep diveThe replace is not what breaks an install — it is what hides the break
Every module except the root carries a replace pointing at a sibling directory
so the repository builds during development.
A replace directive is honoured only in the main module. When somebody else
runs go get, their module is the main module, so ours is ignored entirely.
That means the require line is the only thing a consumer's build sees — and
while it says v0.0.0, the resolve fails against a version that will never
exist. The replace made it work locally, which is exactly why nobody noticed.
Locally the repository uses a go.work workspace instead, which Go never
consults when skyl is somebody's dependency. Unlike a replace, it cannot leak.
CI builds every module with GOWORK=off for the same reason: with the
workspace active, a broken require resolves from the local directory and
nobody notices until a user tries to install a published version.
Why the order is not optional#
Each module's require must point at a version that already exists on the
proxy. So:
- Tag and push the root module first.
- Rewrite the submodules'
requireto that version, drop theirreplace, tag and push each.
Reversing that publishes a module requiring a version that does not exist yet — and since tags on the module proxy are immutable, the fix is a new version rather than a corrected one.
The CI guard#
The checklist#
CHANGELOG.md— move Unreleased to the new version, with migration notes for anything breaking.- Confirm CI is green on
main, including the sandbox suite. - Run
scripts/release.sh vX.Y.Z. - Tag and push the root module. Wait for the proxy.
- Tag and push each submodule, in the order the script prompts.
- Verify from outside the repository:
cd $(mktemp -d) && go mod init check
GOWORK=off go get github.com/BAGOMBEKA-JOB-DEV/skyl@vX.Y.Z
GOWORK=off go get github.com/BAGOMBEKA-JOB-DEV/skyl/provider/anthropic@vX.Y.ZGOWORK=off matters — without it you may be resolving from a local workspace
and proving nothing.
Go version floors#
CI builds each module against its own floor, so a go directive that drifts
from what the code needs fails the build rather than reaching a user. Since Go
1.21 the directive is a hard requirement, so a floor raised carelessly locks out
users for no reason.
| Module | Import path | Go | Dependencies |
|---|---|---|---|
| skyl | github.com/BAGOMBEKA-JOB-DEV/skyl | 1.22 | noneThe core library. Zero external dependencies. |
| provider/anthropic | github.com/BAGOMBEKA-JOB-DEV/skyl/provider/anthropic | 1.24 | anthropic-sdk-goA separate module, because the official SDK brings a dozen transitive dependencies. |
| gateway | github.com/BAGOMBEKA-JOB-DEV/skyl/gateway | 1.25 | go-chi/chi, skyl/otelThe optional HTTP service. Importing the core library never pulls in chi. |
| otel | github.com/BAGOMBEKA-JOB-DEV/skyl/otel | 1.25 | go.opentelemetry.io/otelOpenTelemetry instrumentation. Nobody who does not want it pays for it. |
Pre-v1#
Breaking changes may land in minor releases, and must appear in the changelog with a migration note. See Versions.