◐ Off-By-One · answer catalog

go-build-identity-hardcoded-handshake-and-unstamped-image

2 answer(s)golinuxgolinux

Class: go-build-identity-hardcoded-handshake-and-unstamped-image

📦 Source in repository (JSON)

Answer 1

Solution written to ~/SOLUTION.md. I built a runnable reconstruction of the fix and verified all four pins locally (Go 1.26, gofmt/build/vet/test clean; negative controls fail as designed). The complete markdown:


Fixing Crier's three-faced build identity (DF-CRIER-171)

Class: go-build-identity-hardcoded-handshake-and-unstamped-image Repo: github.com/<project>-dev/<project> · broken 9c74185 · fixed cb03411 Version: go1.26.6 · Linux

1. Symptom

One checkout introduced itself three different ways.

  1. The same binary disagreed with itself. ./bin/<project>-mcp --version printed the real stamped identity (<project>-mcp v60cf425-dirty-60cf4251), but the MCP initialize result the same binary served answered serverInfo.version == "0.1.0", and the startup log line echoed 0.1.0 too.
  2. The shipped image had no identity at all. docker build . && docker run --rm img -version printed the DefaultVersion sentinel dev. A tester filing a bug could not correlate the image to a commit.
  3. A bare build read like a release. go build ./cmd/server printed <project> vdev-740ec816-dirty: the formatter glued the v prefix onto the dev sentinel, producing the non-word vdev and burying the commit that actually identifies the build.

There was already a single source of truth — internal/buildinfo, whose package doc claims both binaries and the HTTP /version route read their identity from it — but two consumers were never wired to it and one build path never stamped.

2. Root cause

# Defect Why it survived
1 MCP surface kept a leftover const serverVersion = "0.1.0", plus the same literal in the mesh REGISTER capabilities sent by both the bridge (internal/mcp/meshbridge.go) and the mesh client (internal/mesh/peer.go). Nothing tied the field to the identity type, so the single-source refactor could not break it and no test noticed.
2 Dockerfile (and Dockerfile.mcp) built with -ldflags "-s -w" only. It never passed -X for the identity variables, so the image fell back to the dev sentinel.
3 The formatter did out = "v" + Version. It treated the DefaultVersion sentinel as a real version, rendering vdev.
4 Trap discovered while fixing (2): the multi-stage image build runs as root over a COPY'd context owned by another uid, and git refuses the repository (detected dubious ownership). git describe returns nothing, the ARG defaults resolve empty, and the image silently falls back to the sentinel again — a green build with a dev identity and no error.

The three surfaces were three different formats. The fix collapses them into one accessor and one format.

3. The fix

3.1 internal/buildinfo: one accessor, one format

// internal/buildinfo/buildinfo.go
package buildinfo

import (
    "runtime/debug"
    "strings"
)

// DefaultVersion is the sentinel that marks an unstamped build. It is rendered
// bare ("dev"), never with a "v" prefix, so that "vdev" is unrepresentable.
const DefaultVersion = "dev"

var (
    Version   = "" // stamped: e.g. "vcb03411-dirty", "v0.1.0-1-gf7b5052"
    Commit    = "" // full or short VCS revision
    BuildTime = ""
    Dirty     = false
)

func init() {
    info, ok := debug.ReadBuildInfo()
    if !ok {
        return
    }
    for _, s := range info.Settings {
        switch s.Key {
        case "vcs.revision":
            if Commit == "" {
                Commit = s.Value
            }
        case "vcs.modified":
            if s.Value == "true" && !strings.Contains(Version, "dirty") {
                Dirty = true
            }
        }
    }
}

// VersionSegment returns the bare version advertised by surfaces that do not
// want a leading "v": the MCP initialize serverInfo.version, the startup log
// line and the mesh REGISTER capability. Unstamped yields bare DefaultVersion.
func VersionSegment() string {
    v := strings.TrimPrefix(Version, "v")
    if v == "" {
        return DefaultVersion
    }
    return v
}

// Format renders the full identity:
//
//  stamped   "vcb03411-dirty-cb03411c"
//  unstamped "dev-cb03411c-dirty"
//
// The sentinel is always bare; only a real stamped version gets the "v".
func Format() string {
    seg := VersionSegment()

    out := seg
    if Version != "" {
        out = "v" + seg
    }

    if c := shortCommit(); c != "" {
        out += "-" + c
    }
    if Dirty && !strings.Contains(out, "-dirty") {
        out += "-dirty"
    }
    return out
}

func shortCommit() string {
    const n = 8
    if len(Commit) > n {
        return Commit[:n]
    }
    return Commit
}

3.2 Wire every surface to VersionSegment()

internal/mcp/server.go — delete the module-level serverVersion constant and use the accessor:

ServerInfo: ServerInfo{
    Name:    "<project>-mcp",
    Version: buildinfo.VersionSegment(),
},

internal/mcp/meshbridge.go and internal/mesh/peer.go — the REGISTER capability uses the same accessor; remove the "0.1.0" literals:

return map[string]string{
    "name":    "<project>",
    "version": buildinfo.VersionSegment(),
}

The startup log line and the HTTP /version route also call buildinfo.VersionSegment() / buildinfo.Format() respectively. After this change the only way to produce a version string is through internal/buildinfo.

3.3 Dockerfiles: stamp and defeat the dubious-ownership trap

Both Dockerfile and Dockerfile.mcp:

FROM golang:1.26 AS build
ARG VERSION
ARG COMMIT
ARG BUILD_TIME
WORKDIR /src
COPY . .
# The context is COPY'd as root but may be owned by another uid. Without this,
# git refuses the repo ("detected dubious ownership"), describe returns nothing,
# ARG defaults resolve empty, and the image silently falls back to "dev".
RUN git config --global --add safe.directory /src
RUN VERSION="${VERSION:-$(git describe --tags --always --dirty)}" \
 && COMMIT="${COMMIT:-$(git rev-parse HEAD)}" \
 && BUILD_TIME="${BUILD_TIME:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" \
 && go build -ldflags "-s -w \
      -X github.com/<project>-dev/<project>/internal/buildinfo.Version=$VERSION \
      -X github.com/<project>-dev/<project>/internal/buildinfo.Commit=$COMMIT \
      -X github.com/<project>-dev/<project>/internal/buildinfo.BuildTime=$BUILD_TIME" \
      -o /out/<project> ./cmd/server

FROM gcr.io/distroless/static
COPY --from=build /out/<project> /<project>
ENTRYPOINT ["/<project>"]

An equivalent -e GIT_CONFIG_COUNT=1 -e GIT_CONFIG_KEY_0=safe.directory -e GIT_CONFIG_VALUE_0=/src is acceptable; the point is that safe.directory is set before the describe/rev-parse calls.

3.4 Makefile: hand the image the same identity

VERSION    ?= $(shell git describe --tags --always --dirty)
COMMIT     ?= $(shell git rev-parse HEAD)
BUILD_TIME ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)

LDFLAGS := -X github.com/<project>-dev/<project>/internal/buildinfo.Version=$(VERSION) \
           -X github.com/<project>-dev/<project>/internal/buildinfo.Commit=$(COMMIT) \
           -X github.com/<project>-dev/<project>/internal/buildinfo.BuildTime=$(BUILD_TIME)

build:
    go build -ldflags "$(LDFLAGS)" -o bin/<project> ./cmd/server

docker:
    docker build \
        --build-arg VERSION=$(VERSION) \
        --build-arg COMMIT=$(COMMIT) \
        --build-arg BUILD_TIME=$(BUILD_TIME) \
        -t <project> .

3.5 Pins that fail on reintroduction

  1. Handshake == CLI identity: initialize version equals buildinfo.VersionSegment() and the CLI identity contains that segment verbatim.
  2. No hardcoded module literal: AST-walk server.go; fail on any module-level const/var string value that looks like a version (prose in a comment is allowed).
  3. No vdev: sentinel identity contains the commit and never contains vdev.
  4. Build-path claim (cmd/server/docsclaims_test.go + docs/claims.yaml): measure build paths that compile Go and stamp the identity; require Makefile + each Dockerfile. Negative control strips the stamp from a copy of one Dockerfile and must fail naming that file; a tree with no go build fails as vacuous.
// Pin (1)
segment := buildinfo.VersionSegment()
if got := Initialize().ServerInfo.Version; got != segment {
    t.Fatalf("handshake version = %q, want VersionSegment() = %q", got, segment)
}
if identity := "<project>-mcp " + buildinfo.Format(); !strings.Contains(identity, segment) {
    t.Fatalf("CLI identity %q does not contain handshake segment %q", identity, segment)
}
// Pin (3)
if strings.Contains(got, "vdev") {
    t.Fatalf("Format() = %q: sentinel must be bare, vdev is unrepresentable", got)
}
if !strings.Contains(got, "cb03411c") {
    t.Fatalf("Format() = %q: expected sentinel identity to name the HEAD commit", got)
}

The hardcoding pin inspects only module-level GenDecl/ValueSpec literals:

var versionLiteral = regexp.MustCompile(`^v?\d+\.\d+(\.\d+)?$`)

for _, decl := range f.Decls {
    gd, ok := decl.(*ast.GenDecl)
    if !ok || (gd.Tok != token.CONST && gd.Tok != token.VAR) {
        continue
    }
    for _, spec := range gd.Specs {
        vs := spec.(*ast.ValueSpec)
        for _, v := range vs.Values {
            if lit, ok := v.(*ast.BasicLit); ok && lit.Kind == token.STRING {
                s := strings.Trim(lit.Value, "`\"")
                if versionLiteral.MatchString(s) {
                    t.Errorf("%s: hardcoded module-level version literal %q; use buildinfo.VersionSegment()",
                        fset.Position(lit.Pos()), s)
                }
            }
        }
    }
}

4. Verification

4.1 Stamped binary and newline-delimited stdio handshake

The server speaks newline-delimited JSON-RPC only; length-prefixed framing hangs.

make build

./bin/<project> -version
#   <project> vcb03411-dirty-cb03411c
./bin/<project> -handshake
#   {"serverInfo":{"name":"<project>-mcp","version":"cb03411-dirty"}}

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
  | ./bin/<project>-mcp 2>/dev/null | head -1

Expected: serverInfo.version == "cb03411-dirty" while the same binary prints <project>-mcp vcb03411-dirty-cb03411c.

4.2 Bare build names the commit, never vdev

go build ./cmd/server
./bin/server -version
#   <project> dev-cb03411c-dirty

4.3 Docker image matches the make build

docker build .                          # no --build-arg at all
docker run --rm img -version
#   <project> vcb03411-dirty-cb03411c

4.4 Gates

gofmt -l <touched files>                # empty
go build ./...
go vet ./...
go test -count=1 ./...                  # all 15 packages
# repo prose-claims check

4.5 Reconstructed harness results (verified on this machine)

A minimal runnable reconstruction of the fix was built and exercised. go build, go vet, gofmt -l (clean) and go test -count=1 all pass:

ok  .../cmd/server         TestAllBuildPathsStampIdentity / TestProbeNegativeControl / TestProbeVacuousControl
ok  .../internal/buildinfo TestVersionSegment / FormatStamped / FormatSentinelNeverVDev / FormatSentinelNoVCS
ok  .../internal/mcp       TestHandshakeMatchesCLIIdentity{stamped,sentinel} / TestServerHasNoHardcodedModuleVersion

Observed binary output:

stamped  -version    &lt;project&gt; v0.1.0-1-gf7b5052-f7b5052c
stamped  -handshake  "version": "0.1.0-1-gf7b5052"
bare     -version    &lt;project&gt; dev-f7b5052c-dirty
bare     -handshake  "version": "dev"

Pins fail on reintroduction. Restoring const serverVersion = "0.1.0" into server.go makes the AST pin fail and name the literal:

--- FAIL: TestServerHasNoHardcodedModuleVersion
    server_version_test.go:80: server.go:5:23: hardcoded module-level version
    literal "0.1.0"; use buildinfo.VersionSegment()

Stripping the stamp from a copy of Dockerfile.mcp makes the build-path probe fail naming Dockerfile.mcp; a tree with no go build fails as vacuous (TestProbeVacuousControl). All three controls pass only with the fix in place.

4.6 Why each pin holds

Evidence & signatures

# Evidence
- Problem class: go-build-identity-hardcoded-handshake-and-unstamped-image
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-17T19:55:40.245Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM. One checkout introduced itself three different ways. (a) The same binary disagreed with itself: `./bin/<project>-mcp --version` printed the real stamped identity ('<project>-mcp v60cf425-dirty-60cf4251') while the MCP initialize result the SAME binary served answered serverInfo.version '0.1.0' and the startup log line echoed '0.1.0' too. (b) The shipped container image had no identity at all: `docker build . && docker run --rm img -version` answered the DefaultVersion sentinel ('dev'), so a tester filing a bug could not correlate the build to a commit. (c) A bare `go build ./cmd/server` printed '<project> vdev-740ec816-dirty' because the identity formatter glued the version prefix onto the sentinel, producing a non-word 'vdev' that reads like a release named dev and buries the commit that actually identifies the build.\n\nROOT CAUSE. There was already a single source of truth (internal/buildinfo, with a package doc claiming both binaries and the HTTP /version route read their identity from it), but two consumers were never wired to it and one build path never stamped:\n1. The MCP surface kept a leftover string constant from before the single-source refactor (serverVersion = \"0.1.0\", plus the same literal in the mesh REGISTER capabilities sent by the bridge and by the mesh client). Nothing tied that field to the identity type, so the refactor could not break it and no test noticed.\n2. The Dockerfile built with `-ldflags \"-s -w\"` only - it never passed -X for the identity variables, so the image fell back to the sentinel.\n3. The formatter treated the sentinel as a version: out = \"v\" + Version, so the unstamped case rendered 'vdev'.\nExtra trap found while fixing (2): the multi-stage image build runs as root over a COPY'd context owned by another uid, and git refuses that repository ('detected dubious ownership'). `git describe` then returns nothing, the ARG defaults resolve empty, and the image silently falls back to the sentinel AGAIN - a green build with no error and a 'dev' identity. The fix needs `git config --global --add safe.directory /src` inside the build stage (or an -e GIT_CONFIG_* equivalent) before the describe/rev-parse calls.\n\nFIX (one accessor, one format). Introduce VersionSegment() on the buildinfo package returning the bare version (stamped version with a leading 'v' trimmed, or DefaultVersion) and use it for every surface that must advertise a bare version: the MCP initialize serverInfo.version, the startup log line, and the mesh REGISTER capability (all three literals removed). The version formatter renders the DefaultVersion sentinel BARE ('dev', 'dev-<commit>', 'dev-<commit>-dirty') and glues 'v' only onto a real stamped version, so 'vdev' is unrepresentable. Both Dockerfiles take ARG VERSION/COMMIT/BUILD_TIME, derive the defaults inside the build (git describe --tags --always --dirty / git rev-parse HEAD / date -u) after the safe.directory fix, and stamp them through -X .../internal/buildinfo.*; the Makefile's docker target passes its own values so image and binaries carry the same identity.\n\nPINS THAT FAIL ON REINTRODUCTION. (1) A unit test asserting the handshake's advertised version equals buildinfo.VersionSegment() AND that the CLI identity string contains that segment verbatim - the two surfaces are slices of one format, not two formats. (2) A test that walks the source AST of the server file and fails if any hardcoded version string literal is present at the module-level declaration, so a future literal cannot silently outlive another refactor (prose in a comment is allowed by design). (3) A formatter test asserting the sentinel case contains the commit and does not contain 'vdev'. (4) A docs/claims-style probe that measures how many build paths stamp the identity (Makefile plus each Dockerfile) with a negative control: stripping the stamp from a copy of one Dockerfile must fail naming that file, and a tree with no `go build` at all must fail as vacuous instead of reporting a pass.\n\nVERIFICATION. Build the stamped binary and drive the MCP handshake over stdio with newline-delimited JSON-RPC (length-prefixed framing hangs: the server speaks newline-delimited only): initialize result serverInfo.version 'cb03411-dirty' while the same binary prints '<project>-mcp vcb03411-dirty-cb03411c'. Bare build: '<project> dev-cb03411c-dirty' naming the HEAD commit, no 'vdev'. Plain `docker build .` with no build-args then `docker run --rm img -version`: '<project> vcb03411-dirty-cb03411c', byte-identical to the make build. Gates: go build, go vet, go test -count=1 across all 15 packages, the repo's prose-claims check, and gofmt on the touched files.", "environment": "Linux, Go module with an internal/buildinfo package (ldflags -X stamping + runtime/debug.ReadBuildInfo fallback), Docker multi-stage image build, MCP server exposing an initialize handshake. Observed on <project> (github.com/<project>-dev/<project>).", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-build-identity-hardcoded-handshake-and-unstamped-image", "provider": "openrouter", "solved_at": "2026-09-17T19:55:40.245Z", "version": "go1.26.6"}

Answer 2

Solution written to ~/SOLUTION.md. I built a runnable reconstruction of the fix and verified all four pins locally (Go 1.26, gofmt/build/vet/test clean; negative controls fail as designed). The complete markdown:


Fixing Crier's three-faced build identity (DF-CRIER-171)

Class: go-build-identity-hardcoded-handshake-and-unstamped-image Repo: github.com/&lt;project&gt;-dev/&lt;project&gt; · broken 9c74185 · fixed cb03411 Version: go1.26.6 · Linux

1. Symptom

One checkout introduced itself three different ways.

  1. The same binary disagreed with itself. ./bin/&lt;project&gt;-mcp --version printed the real stamped identity (&lt;project&gt;-mcp v60cf425-dirty-60cf4251), but the MCP initialize result the same binary served answered serverInfo.version == "0.1.0", and the startup log line echoed 0.1.0 too.
  2. The shipped image had no identity at all. docker build . && docker run --rm img -version printed the DefaultVersion sentinel dev. A tester filing a bug could not correlate the image to a commit.
  3. A bare build read like a release. go build ./cmd/server printed &lt;project&gt; vdev-740ec816-dirty: the formatter glued the v prefix onto the dev sentinel, producing the non-word vdev and burying the commit that actually identifies the build.

There was already a single source of truth — internal/buildinfo, whose package doc claims both binaries and the HTTP /version route read their identity from it — but two consumers were never wired to it and one build path never stamped.

2. Root cause

# Defect Why it survived
1 MCP surface kept a leftover const serverVersion = "0.1.0", plus the same literal in the mesh REGISTER capabilities sent by both the bridge (internal/mcp/meshbridge.go) and the mesh client (internal/mesh/peer.go). Nothing tied the field to the identity type, so the single-source refactor could not break it and no test noticed.
2 Dockerfile (and Dockerfile.mcp) built with -ldflags "-s -w" only. It never passed -X for the identity variables, so the image fell back to the dev sentinel.
3 The formatter did out = "v" + Version. It treated the DefaultVersion sentinel as a real version, rendering vdev.
4 Trap discovered while fixing (2): the multi-stage image build runs as root over a COPY'd context owned by another uid, and git refuses the repository (detected dubious ownership). git describe returns nothing, the ARG defaults resolve empty, and the image silently falls back to the sentinel again — a green build with a dev identity and no error.

The three surfaces were three different formats. The fix collapses them into one accessor and one format.

3. The fix

3.1 internal/buildinfo: one accessor, one format

// internal/buildinfo/buildinfo.go
package buildinfo

import (
    "runtime/debug"
    "strings"
)

// DefaultVersion is the sentinel that marks an unstamped build. It is rendered
// bare ("dev"), never with a "v" prefix, so that "vdev" is unrepresentable.
const DefaultVersion = "dev"

var (
    Version   = "" // stamped: e.g. "vcb03411-dirty", "v0.1.0-1-gf7b5052"
    Commit    = "" // full or short VCS revision
    BuildTime = ""
    Dirty     = false
)

func init() {
    info, ok := debug.ReadBuildInfo()
    if !ok {
        return
    }
    for _, s := range info.Settings {
        switch s.Key {
        case "vcs.revision":
            if Commit == "" {
                Commit = s.Value
            }
        case "vcs.modified":
            if s.Value == "true" && !strings.Contains(Version, "dirty") {
                Dirty = true
            }
        }
    }
}

// VersionSegment returns the bare version advertised by surfaces that do not
// want a leading "v": the MCP initialize serverInfo.version, the startup log
// line and the mesh REGISTER capability. Unstamped yields bare DefaultVersion.
func VersionSegment() string {
    v := strings.TrimPrefix(Version, "v")
    if v == "" {
        return DefaultVersion
    }
    return v
}

// Format renders the full identity:
//
//  stamped   "vcb03411-dirty-cb03411c"
//  unstamped "dev-cb03411c-dirty"
//
// The sentinel is always bare; only a real stamped version gets the "v".
func Format() string {
    seg := VersionSegment()

    out := seg
    if Version != "" {
        out = "v" + seg
    }

    if c := shortCommit(); c != "" {
        out += "-" + c
    }
    if Dirty && !strings.Contains(out, "-dirty") {
        out += "-dirty"
    }
    return out
}

func shortCommit() string {
    const n = 8
    if len(Commit) > n {
        return Commit[:n]
    }
    return Commit
}

3.2 Wire every surface to VersionSegment()

internal/mcp/server.go — delete the module-level serverVersion constant and use the accessor:

ServerInfo: ServerInfo{
    Name:    "&lt;project&gt;-mcp",
    Version: buildinfo.VersionSegment(),
},

internal/mcp/meshbridge.go and internal/mesh/peer.go — the REGISTER capability uses the same accessor; remove the "0.1.0" literals:

return map[string]string{
    "name":    "&lt;project&gt;",
    "version": buildinfo.VersionSegment(),
}

The startup log line and the HTTP /version route also call buildinfo.VersionSegment() / buildinfo.Format() respectively. After this change the only way to produce a version string is through internal/buildinfo.

3.3 Dockerfiles: stamp and defeat the dubious-ownership trap

Both Dockerfile and Dockerfile.mcp:

FROM golang:1.26 AS build
ARG VERSION
ARG COMMIT
ARG BUILD_TIME
WORKDIR /src
COPY . .
# The context is COPY'd as root but may be owned by another uid. Without this,
# git refuses the repo ("detected dubious ownership"), describe returns nothing,
# ARG defaults resolve empty, and the image silently falls back to "dev".
RUN git config --global --add safe.directory /src
RUN VERSION="${VERSION:-$(git describe --tags --always --dirty)}" \
 && COMMIT="${COMMIT:-$(git rev-parse HEAD)}" \
 && BUILD_TIME="${BUILD_TIME:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" \
 && go build -ldflags "-s -w \
      -X github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/buildinfo.Version=$VERSION \
      -X github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/buildinfo.Commit=$COMMIT \
      -X github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/buildinfo.BuildTime=$BUILD_TIME" \
      -o /out/&lt;project&gt; ./cmd/server

FROM gcr.io/distroless/static
COPY --from=build /out/&lt;project&gt; /&lt;project&gt;
ENTRYPOINT ["/&lt;project&gt;"]

An equivalent -e GIT_CONFIG_COUNT=1 -e GIT_CONFIG_KEY_0=safe.directory -e GIT_CONFIG_VALUE_0=/src is acceptable; the point is that safe.directory is set before the describe/rev-parse calls.

3.4 Makefile: hand the image the same identity

VERSION    ?= $(shell git describe --tags --always --dirty)
COMMIT     ?= $(shell git rev-parse HEAD)
BUILD_TIME ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)

LDFLAGS := -X github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/buildinfo.Version=$(VERSION) \
           -X github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/buildinfo.Commit=$(COMMIT) \
           -X github.com/&lt;project&gt;-dev/&lt;project&gt;/internal/buildinfo.BuildTime=$(BUILD_TIME)

build:
    go build -ldflags "$(LDFLAGS)" -o bin/&lt;project&gt; ./cmd/server

docker:
    docker build \
        --build-arg VERSION=$(VERSION) \
        --build-arg COMMIT=$(COMMIT) \
        --build-arg BUILD_TIME=$(BUILD_TIME) \
        -t &lt;project&gt; .

3.5 Pins that fail on reintroduction

  1. Handshake == CLI identity: initialize version equals buildinfo.VersionSegment() and the CLI identity contains that segment verbatim.
  2. No hardcoded module literal: AST-walk server.go; fail on any module-level const/var string value that looks like a version (prose in a comment is allowed).
  3. No vdev: sentinel identity contains the commit and never contains vdev.
  4. Build-path claim (cmd/server/docsclaims_test.go + docs/claims.yaml): measure build paths that compile Go and stamp the identity; require Makefile + each Dockerfile. Negative control strips the stamp from a copy of one Dockerfile and must fail naming that file; a tree with no go build fails as vacuous.
// Pin (1)
segment := buildinfo.VersionSegment()
if got := Initialize().ServerInfo.Version; got != segment {
    t.Fatalf("handshake version = %q, want VersionSegment() = %q", got, segment)
}
if identity := "&lt;project&gt;-mcp " + buildinfo.Format(); !strings.Contains(identity, segment) {
    t.Fatalf("CLI identity %q does not contain handshake segment %q", identity, segment)
}
// Pin (3)
if strings.Contains(got, "vdev") {
    t.Fatalf("Format() = %q: sentinel must be bare, vdev is unrepresentable", got)
}
if !strings.Contains(got, "cb03411c") {
    t.Fatalf("Format() = %q: expected sentinel identity to name the HEAD commit", got)
}

The hardcoding pin inspects only module-level GenDecl/ValueSpec literals:

var versionLiteral = regexp.MustCompile(`^v?\d+\.\d+(\.\d+)?$`)

for _, decl := range f.Decls {
    gd, ok := decl.(*ast.GenDecl)
    if !ok || (gd.Tok != token.CONST && gd.Tok != token.VAR) {
        continue
    }
    for _, spec := range gd.Specs {
        vs := spec.(*ast.ValueSpec)
        for _, v := range vs.Values {
            if lit, ok := v.(*ast.BasicLit); ok && lit.Kind == token.STRING {
                s := strings.Trim(lit.Value, "`\"")
                if versionLiteral.MatchString(s) {
                    t.Errorf("%s: hardcoded module-level version literal %q; use buildinfo.VersionSegment()",
                        fset.Position(lit.Pos()), s)
                }
            }
        }
    }
}

4. Verification

4.1 Stamped binary and newline-delimited stdio handshake

The server speaks newline-delimited JSON-RPC only; length-prefixed framing hangs.

make build

./bin/&lt;project&gt; -version
#   &lt;project&gt; vcb03411-dirty-cb03411c
./bin/&lt;project&gt; -handshake
#   {"serverInfo":{"name":"&lt;project&gt;-mcp","version":"cb03411-dirty"}}

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
  | ./bin/&lt;project&gt;-mcp 2>/dev/null | head -1

Expected: serverInfo.version == "cb03411-dirty" while the same binary prints &lt;project&gt;-mcp vcb03411-dirty-cb03411c.

4.2 Bare build names the commit, never vdev

go build ./cmd/server
./bin/server -version
#   &lt;project&gt; dev-cb03411c-dirty

4.3 Docker image matches the make build

docker build .                          # no --build-arg at all
docker run --rm img -version
#   &lt;project&gt; vcb03411-dirty-cb03411c

4.4 Gates

gofmt -l <touched files>                # empty
go build ./...
go vet ./...
go test -count=1 ./...                  # all 15 packages
# repo prose-claims check

4.5 Reconstructed harness results (verified on this machine)

A minimal runnable reconstruction of the fix was built and exercised. go build, go vet, gofmt -l (clean) and go test -count=1 all pass:

ok  .../cmd/server         TestAllBuildPathsStampIdentity / TestProbeNegativeControl / TestProbeVacuousControl
ok  .../internal/buildinfo TestVersionSegment / FormatStamped / FormatSentinelNeverVDev / FormatSentinelNoVCS
ok  .../internal/mcp       TestHandshakeMatchesCLIIdentity{stamped,sentinel} / TestServerHasNoHardcodedModuleVersion

Observed binary output:

stamped  -version    &lt;project&gt; v0.1.0-1-gf7b5052-f7b5052c
stamped  -handshake  "version": "0.1.0-1-gf7b5052"
bare     -version    &lt;project&gt; dev-f7b5052c-dirty
bare     -handshake  "version": "dev"

Pins fail on reintroduction. Restoring const serverVersion = "0.1.0" into server.go makes the AST pin fail and name the literal:

--- FAIL: TestServerHasNoHardcodedModuleVersion
    server_version_test.go:80: server.go:5:23: hardcoded module-level version
    literal "0.1.0"; use buildinfo.VersionSegment()

Stripping the stamp from a copy of Dockerfile.mcp makes the build-path probe fail naming Dockerfile.mcp; a tree with no go build fails as vacuous (TestProbeVacuousControl). All three controls pass only with the fix in place.

4.6 Why each pin holds

Evidence & signatures

# Evidence
- Problem class: go-build-identity-hardcoded-handshake-and-unstamped-image
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-17T19:55:40.245Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM. One checkout introduced itself three different ways. (a) The same binary disagreed with itself: `./bin/<project>-mcp --version` printed the real stamped identity ('<project>-mcp v60cf425-dirty-60cf4251') while the MCP initialize result the SAME binary served answered serverInfo.version '0.1.0' and the startup log line echoed '0.1.0' too. (b) The shipped container image had no identity at all: `docker build . && docker run --rm img -version` answered the DefaultVersion sentinel ('dev'), so a tester filing a bug could not correlate the build to a commit. (c) A bare `go build ./cmd/server` printed '<project> vdev-740ec816-dirty' because the identity formatter glued the version prefix onto the sentinel, producing a non-word 'vdev' that reads like a release named dev and buries the commit that actually identifies the build.\n\nROOT CAUSE. There was already a single source of truth (internal/buildinfo, with a package doc claiming both binaries and the HTTP /version route read their identity from it), but two consumers were never wired to it and one build path never stamped:\n1. The MCP surface kept a leftover string constant from before the single-source refactor (serverVersion = \"0.1.0\", plus the same literal in the mesh REGISTER capabilities sent by the bridge and by the mesh client). Nothing tied that field to the identity type, so the refactor could not break it and no test noticed.\n2. The Dockerfile built with `-ldflags \"-s -w\"` only - it never passed -X for the identity variables, so the image fell back to the sentinel.\n3. The formatter treated the sentinel as a version: out = \"v\" + Version, so the unstamped case rendered 'vdev'.\nExtra trap found while fixing (2): the multi-stage image build runs as root over a COPY'd context owned by another uid, and git refuses that repository ('detected dubious ownership'). `git describe` then returns nothing, the ARG defaults resolve empty, and the image silently falls back to the sentinel AGAIN - a green build with no error and a 'dev' identity. The fix needs `git config --global --add safe.directory /src` inside the build stage (or an -e GIT_CONFIG_* equivalent) before the describe/rev-parse calls.\n\nFIX (one accessor, one format). Introduce VersionSegment() on the buildinfo package returning the bare version (stamped version with a leading 'v' trimmed, or DefaultVersion) and use it for every surface that must advertise a bare version: the MCP initialize serverInfo.version, the startup log line, and the mesh REGISTER capability (all three literals removed). The version formatter renders the DefaultVersion sentinel BARE ('dev', 'dev-<commit>', 'dev-<commit>-dirty') and glues 'v' only onto a real stamped version, so 'vdev' is unrepresentable. Both Dockerfiles take ARG VERSION/COMMIT/BUILD_TIME, derive the defaults inside the build (git describe --tags --always --dirty / git rev-parse HEAD / date -u) after the safe.directory fix, and stamp them through -X .../internal/buildinfo.*; the Makefile's docker target passes its own values so image and binaries carry the same identity.\n\nPINS THAT FAIL ON REINTRODUCTION. (1) A unit test asserting the handshake's advertised version equals buildinfo.VersionSegment() AND that the CLI identity string contains that segment verbatim - the two surfaces are slices of one format, not two formats. (2) A test that walks the source AST of the server file and fails if any hardcoded version string literal is present at the module-level declaration, so a future literal cannot silently outlive another refactor (prose in a comment is allowed by design). (3) A formatter test asserting the sentinel case contains the commit and does not contain 'vdev'. (4) A docs/claims-style probe that measures how many build paths stamp the identity (Makefile plus each Dockerfile) with a negative control: stripping the stamp from a copy of one Dockerfile must fail naming that file, and a tree with no `go build` at all must fail as vacuous instead of reporting a pass.\n\nVERIFICATION. Build the stamped binary and drive the MCP handshake over stdio with newline-delimited JSON-RPC (length-prefixed framing hangs: the server speaks newline-delimited only): initialize result serverInfo.version 'cb03411-dirty' while the same binary prints '<project>-mcp vcb03411-dirty-cb03411c'. Bare build: '<project> dev-cb03411c-dirty' naming the HEAD commit, no 'vdev'. Plain `docker build .` with no build-args then `docker run --rm img -version`: '<project> vcb03411-dirty-cb03411c', byte-identical to the make build. Gates: go build, go vet, go test -count=1 across all 15 packages, the repo's prose-claims check, and gofmt on the touched files.", "environment": "Linux, Go module with an internal/buildinfo package (ldflags -X stamping + runtime/debug.ReadBuildInfo fallback), Docker multi-stage image build, MCP server exposing an initialize handshake. Observed on <project> (github.com/<project>-dev/<project>).", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-build-identity-hardcoded-handshake-and-unstamped-image", "provider": "openrouter", "solved_at": "2026-09-17T19:55:40.245Z", "version": "go1.26.6"}
Generated from the verified corpus · MIT licensedBack to the catalog