Skip to content

fix(update): recover the release version from build info - #197

Merged
adnaan merged 1 commit into
mainfrom
version-buildinfo
Jul 24, 2026
Merged

fix(update): recover the release version from build info#197
adnaan merged 1 commit into
mainfrom
version-buildinfo

Conversation

@adnaan

@adnaan adnaan commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Problem

A binary installed with go install github.com/livetemplate/prereview@latest reports version dev and can never self-update — --update refuses with ErrDevBuild, whose own message recommends that exact install method. Only goreleaser sets -X main.version; go install doesn't apply it.

Verified before writing anything, in an isolated GOBIN so nothing landed on PATH:

$ GOBIN=$tmp go install github.com/livetemplate/prereview@latest
go: downloading github.com/livetemplate/prereview v0.25.0
$ $tmp/prereview --version
dev

Its build info, however, records the real thing: mod github.com/livetemplate/prereview v0.25.0.

Fix

update.ReleaseVersion falls back to debug.ReadBuildInfo() when the ldflag is absent, wired in via init() in main.go.

The fallback only accepts a bare vX.Y.Z tag. This strictness is the load-bearing part. A local go build embeds a version too, and building this very branch produced:

mod  github.com/livetemplate/prereview  v0.25.0+dirty

An exact release tag on a dirty tree. The obvious predicate — strip build metadata, accept valid semver — would have let that build claim to be v0.25.0, and the on-run update check would then overwrite a developer's own working binary with a download. So isReleaseTag rejects on any suffix rather than parsing past it: pseudo-versions, +dirty, and (devel) all stay dev.

A clean build sitting exactly on a release tag does become self-updatable. That's intentional and safe — no uncommitted work to lose, and it's by definition an older release.

The leading v is stripped so --version reads identically whether the binary came from goreleaser (which stamps 0.25.0) or go install (whose build info says v0.25.0).

ErrDevBuild's text now says the binary "was built from a source tree", which is what dev actually means once this lands.

Verification

  • 13 isReleaseTag cases + 7 releaseVersion cases, including the real-world pseudo-version v0.24.5-0.20260723204349-7ab83524cad7+dirty
  • a local go build still prints dev and --update still refuses — the safety direction, checked by hand
  • go vet clean; touched files gofmt-clean
  • no package-level var reads version before init() runs (topUsage is a const; every read is inside run()/runExternal()/main()), so nothing captures the stale "dev" literal
  • nothing parses --version — the goreleaser and Homebrew system "#{bin}/prereview", "--version" calls are exit-code smoke tests — so the added v-stripping breaks no consumer

releaseVersion takes the build-info reader as a parameter, since a test binary's own build info isn't the one under test.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E8bxKS4xC9LisPy7Gacgmq

A `go install github.com/livetemplate/prereview@latest` binary reported
version "dev" and could never self-update — `--update` refused with
ErrDevBuild, whose own message recommends that exact install method.
Only goreleaser sets -X main.version, and `go install` doesn't apply it.

Go does record the module version in the binary's embedded build info,
so fall back to that when the ldflag is absent. The fallback only
accepts a bare vX.Y.Z tag: a local `go build` embeds a version too, but
as a pseudo-version and/or with a "+dirty" suffix, and treating those as
releases would let the on-run check overwrite a developer's own build
with a download. A build sitting on an exact tag with a dirty tree reads
as "v0.25.0+dirty" — the suffix is the only thing separating it from a
genuine release, hence the reject-any-suffix rule rather than parsing
past it.

The leading "v" is stripped so --version reads identically whether the
binary came from goreleaser (which stamps "0.25.0") or `go install`
(whose build info says "v0.25.0").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E8bxKS4xC9LisPy7Gacgmq
@adnaan
adnaan merged commit b441a21 into main Jul 24, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant