feat(version): stamp build identity via ldflags and expose it at runtime - #39
Merged
Conversation
"What is actually running on this host?" had no answer: no version constant in
the backend, no -ldflags in the Dockerfile, no /api/v1/version, and a startup
log line carrying nothing about the build. docker-compose.prod.yaml defaults to
:latest with a watchtower label, so the common case was a mutable tag behind an
unidentifiable binary.
- internal/version holds Version/Commit/BuildDate, defaulting to dev/unknown so
an unstamped binary reads as a local build rather than as a bug.
- The Dockerfile's backend stage stamps them with -ldflags -X. ARG defaults keep
a plain `docker build` at "dev"; ${VAR:-default} rewrites cover the case the
ARG defaults do not, where a --build-arg carries an empty value.
- docker-publish.yml supplies them from the same metadata-action step that names
the image tag, so a published image reports the tag it shipped under.
- GET /api/v1/version is deliberately public and listed in
middleware.PublicPaths, which CSRF consults as well as auth. Monitoring and
support can answer the question without a session; the trade is that the exact
version leaks to an unauthenticated caller, which is already visible in the
image tag.
- Startup log line carries version/commit/built, on the first line so it
survives a death later in startup.
- Settings -> About shows it, linking to the release notes for a semver build.
A typo in an -X symbol path is accepted silently by the linker and leaves the
default in place, so version/ldflags_test.go pins the Dockerfile's -X paths to
the symbols actually declared in the package, and asserts the ARG defaults.
Verified by injecting a typo: the test names the wrong package and reports
Commit as never stamped.
Verified end to end through the real Dockerfile: a plain build reports
version=dev, a build with args reports them exactly, and the endpoint answers
HTTP 200 with auth enabled and no token.
Closes agent-os-r7e
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes
agent-os-r7e.What
Build identity — version, commit, build date — stamped into the binary at link
time, exposed at
GET /api/v1/version, on the startup log line, and in the UI.Before this there was no way to answer "what is actually running on this host?"
Not from the API, not from the UI, not from the logs.
docker-compose.prod.yamldefaults to
:latestwith a watchtower label, so the common case was a mutabletag behind an unidentifiable binary.
Decisions
/api/v1/versionis public. Monitoring and support need the answer withouta session, and the exact version is already visible in the image tag the
operator pulled. It is registered on the unauthenticated
/api/v1group andlisted in
middleware.PublicPaths, because that list is consulted by the CSRFmiddleware as well as auth — so the two stay consistent if the route ever moves
under the protected group. A unit test pins that.
docker-compose.prod.yamlkeeps:latest. The bead asked to considerpinning. A pinned tag in a template file goes stale on every release and nothing
in the repo keeps it current. The comment now says the default is deliberate and
points at the new endpoint for identifying what is actually running.
The check that matters
A typo in an
-Xsymbol path is accepted silently by the linker: no error, nowarning, the default survives, the build succeeds, and the image ships reporting
dev. Two things guard it.backend/internal/version/ldflags_test.gopins the Dockerfile's-Xpaths tothe symbols actually declared in the package (import path via
reflect.TypeFor[Info]().PkgPath(), so a package move is caught too) and assertsthe
ARGdefaults exist. Proven by injecting a typo (version→verison):And the real Dockerfile was built and run three ways:
GET /api/v1/versiondocker build(no args)version=dev commit=unknown built=unknown{"version":"dev",...}HTTP 200--build-arg VERSION=0.9.0 COMMIT=6fb9879 BUILD_DATE=2026-07-31T20:15:00Zversion=0.9.0 commit=6fb9879 built=2026-07-31T20:15:00Z{"version":"0.9.0","commit":"6fb9879","buildDate":"2026-07-31T20:15:00Z"}HTTP 200--build-arg VERSION= COMMIT= BUILD_DATE=(empty)version=dev commit=unknown built=unknown{"version":"dev",...}HTTP 200The third row is why the
RUNline carries${VAR:-default}rewrites: anARGdefault only applies when the arg is absent, so a CI expression that resolved
to nothing would have stamped an empty string — the exact silent failure this
feature exists to prevent.
All three
GETs ran with auth enabled and no token, which is the public-routeproof.
docker-publish.ymlsources the values from the samedocker/metadata-actionstep that names the image tag —
outputs.versionis1.2.3for av1.2.3tagand
manual-<sha>for a dispatch run.BUILD_DATEusesfromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'],which is metadata-action's own documented example for this, so the OCI label and
the API agree. This is the one piece not executed locally — it runs on the next
tag push.
UI
Settings → About shows version, commit and build date, links to the release
notes for a semver build, and says so plainly for a local
devbuild.unknownbecomes an em dash rather than being shown verbatim, since it reads as an error.
Five tests, including the two negative cases (no release link for
devor for amanual-<sha>dispatch build) — both confirmed to fail when their guard isremoved.
Gates
gofmt -lflagsinternal/middleware/auth.go, which was already unformatted onmain(confirmed by stashing) —agent-os-far's territory, not touched here.Test images removed and
docker builder prune -frun afterwards (4.18GB).🤖 Generated with Claude Code