Skip to content

Linked-worktree builds mis-stamp vcs.revision: wrong release-row revision + false stale-binary WARN (self-update path unaffected) #352

Description

@linhdmn

Summary

PR #349 added build-revision self-identification (internal/version.Revision() reads debug.ReadBuildInfo's vcs.revision) so a stale driver binary is loud. A devagent binary built inside a linked git worktree stamps the wrong commit: Go's -buildvcs resolves vcs.revision from the shared common gitdir (the primary checkout's refs/heads/main tip) instead of the worktree's own HEAD. This defeats both consumers of #349's feature — but only for linked-worktree builds (a build in a normal checkout stamps correctly; see the control below).

Evidence — same commit 8ca0688, built two ways (2026-09-12)

Linked worktree (.devagent-worktrees/…, git rev-parse HEAD = 8ca0688) — wrong:

$ go build -o /tmp/dg-wt ./cmd/devagent
$ go version -m /tmp/dg-wt | grep -E 'mod |vcs'
	mod	github.com/FreePeak/devagent	v1.13.6+dirty
	build	vcs=git
	build	vcs.revision=50b03e8d72869f066260f704b355fba112deddfa   # = primary refs/heads/main (#350 merge), predates #349
	build	vcs.time=2026-09-12T20:48:05Z                              # = #350 merge time
	build	vcs.modified=true                                          # worktree tree differs from the 50b03e8 it stamped
$ /tmp/dg-wt --version
0.1.0 (rev 50b03e8d72869f066260f704b355fba112deddfa)
$ git rev-parse HEAD
8ca0688993bf777ae74f7e11028932e33aee3e9a

Plain clone, detached at the same commit 8ca0688 (not a worktree) — correct:

$ git clone <local> /tmp/clone && git -C /tmp/clone checkout 8ca0688
$ go build -C /tmp/clone -o /tmp/dg-clone ./cmd/devagent
$ go version -m /tmp/dg-clone | grep vcs
	build	vcs=git
	build	vcs.revision=8ca0688993bf777ae74f7e11028932e33aee3e9a
	build	vcs.time=2026-09-12T21:24:20Z
	build	vcs.modified=false
$ /tmp/dg-clone --version
0.1.0 (rev 8ca0688993bf777ae74f7e11028932e33aee3e9a)   # == its own HEAD

Same source commit; non-worktree stamps 8ca0688 (modified=false), linked worktree stamps 50b03e8 (modified=true, module reported +dirty). The rev string is confirmed to originate from vcs.revision in the build-info, so this is a worktree VCS-resolution issue, not a non-buildinfo source.

Impact (scoped to linked-worktree builds)

Every devagent binary built inside this repo's per-task worktrees (task/gate builds, ad-hoc verification like this one, record release if invoked from such a binary):

  1. writes a wrong revision into release-created ledger rows (ReleaseRecord.Revision) — e.g. devagent record release … from the worktree binary above recorded "revision":"50b03e8…".
  2. trips RunLoop's stale-binary guard, which compares version.Revision() against git rev-parse HEAD in the loop's cwd. Built-in-worktree stamp (50b03e8) ≠ worktree HEAD (8ca0688) → false [loop] WARN: stale binary on a current binary. The guard is advisory-only (loop still runs), so the cost is a false alarm that erodes the signal Diagnosis complete: loops 290/291 died on transient infra retries mid-implement  #349 was added to provide.

The shipped self-update path is unaffected: scripts/self-update.sh does git pull --ff-only + go build -trimpath ./cmd/devagent in the primary non-worktree checkout, so the live driver stamps correctly (modified=false) and its guard behaves as intended. The live loop does not false-WARN today; the defect is latent for worktree-built binaries.

No user data is corrupted; ReleaseRecord.Tag/SHA are correct (fixed in #351) — only revision is mis-stamped, and the WARN is noise.

Suggested fix direction

Lead: follow the repo's existing -X stamp precedent (#249). .github/workflows/release.yml:134 already stamps -X github.com/FreePeak/devagent/internal/version.Version=${TAG} into every release binary precisely so --version self-reports the published release, and doctor.go:304 already special-cases the unstamped 0.1.0 default. Revision was added as a var of the same stampable shape — so add to the same ldflags:

-X github.com/FreePeak/devagent/internal/version.Revision=$(git rev-parse HEAD)

Applied in release.yml and in scripts/self-update.sh:24 (which currently builds with no -X at all), this is the one-line route that fixes both consumers: release rows name the actual writing commit, and the stale-binary guard compares a trustworthy stamp. When the build injects Revision explicitly, version.Revision() should prefer the injected value over the (possibly mis-resolved) buildinfo vcs.revision. Note RevisionOverride is already the test-only injection seam; the production stamp should ride the same path or a dedicated -X var rather than that name.

Belt-and-braces for the guard (suppresses the WARN only): Go's build-info also exposes vcs.modified, and no git plumbing is needed — go version -m showed vcs.modified=true for the linked-worktree build and false for the clone at the same commit. So the guard can WARN only when Revision() != git rev-parse HEAD and vcs.modified == false, staying quiet on worktree/+dirty builds without extra git rev-parse --absolute-git-dir/--git-common-dir calls. This preserves #349's actual target (a clean driver built from an older commit — self-update builds are clean, modified=false, so a genuinely stale seat still trips). Use it alongside the -X fix, not instead of it: on its own it leaves worktree-built release rows still naming the wrong commit.

Test gap: TestRevision only exercises the unstamped/unknown path, and CI's actions/checkout@v4 plain clone never reproduces the mis-stamp, so the suite stays green despite the defect. Add a case pinning the worktree condition (or the -X-stamped path and the modified-aware guard).

Notes

Surfaced while landing/verifying PR #349 (rebased onto 50b03e8, merged as 7aef356). Filed rather than fixed here because the remediation is a design decision for the feature owner; main is green and the tag/sha fix is independent. The 2026-09-13 PRD footer (#353, merged as 68c36cf, head d8d86fd) links this issue as #349's caveat.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    selfbuildSelf-build loop work item (issue-first selection)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions