Skip to content

Go -buildvcs cannot stamp linked worktrees: wrong-parent stamping, dubious-ownership failures, and the scope of the GOFLAGS mitigation #368

Description

@schmitthub

Findings from an in-container investigation (worktree agent container on feat/cmd-aliases, go1.26.4 linux/arm64) into the VCS/worktree build behavior behind the #361/#365 fixes. Verified by controlled experiment; repro scripts at the bottom.

TL;DR

  • Go's -buildvcs discovery does not recognize a linked worktree's .git file — in every topology we use, stamping is either silently absent or actively wrong. The container failure everyone sees (exit status 128) is only the loudest symptom.
  • The shipped mitigation (GOFLAGS=-buildvcs=false) is correctly scoped: injected only for worktree containers (internal/docker/env.go, if opts.Worktree), overridable via agent.env / instruction env.
  • CI and release builds are unaffected: no GOFLAGS/buildvcs anywhere in .github/workflows/; the Makefile only appends inherited GOFLAGS (GOFLAGS := -trimpath $(GOFLAGS)), so host/CI builds stamp normally. Release metadata additionally comes from ldflags (build.Version, build.Revision via git rev-parse), which works in worktrees.
  • The real loss: binaries built in worktree contexts (host worktrees and worktree containers, including the embedded CP/clawkerd binaries when make clawker runs there) carry no vcs.revision/vcs.time/vcs.modified in debug.BuildInfo. ldflags revision is the only metadata they carry.

Verified behavior matrix (go1.26.4)

Topology What go does Result
(A) plain repo anchors at .git dir stamps correctly
(B) linked worktree outside the parent path (normal host: clawker worktrees live in the XDG data dir) walks past the worktree's .git file, finds no repo builds succeed silently unstamped — even with -buildvcs=true (forced mode only errors when a repo is found but unusable)
(C) linked worktree nested inside the parent path (dev .clawkerlocal setups) walks past the .git file up to the parent's .git directory; runs git status --porcelain / git log with cwd = parent root (confirmed via git shim) stamps the parent checkout's HEAD and dirty state (vcs.modified=true from the parent tree) — wrong metadata, silently
(D) worktree container (= C, parent is a root-owned scaffold with only .git mounted) same parent-root probe git status dies with fatal: detected dubious ownership in repository at '<main repo>'error obtaining VCS status: exit status 128build fails unless -buildvcs=false

Notes:

  • The "dubious ownership" error is not a regression of Worktree containers: root-owned scaffold dir breaks git safe.directory check (go -buildvcs, pre-commit) #361/fix(worktree): protect main .git, fix Go builds, and refuse double-checkout in worktree containers #365 — those changes never attempted to fix git access at the parent root (chown/safe.directory was deliberately rejected as a footgun). It only surfaces when something strips GOFLAGS and go walks to the parent (e.g. sudo go build, deliberately probing with GOFLAGS=). Normal in-container builds, pre-commit hooks, make clawker, and make test are all green.
  • Important: even if the parent-root git probe were unblocked (chown/safe.directory), case (C) shows the stamp would be wrong (parent HEAD, whole tree dirty). There is no configuration of the current topology in which go stamps a worktree build correctly — the comment in internal/docker/env.go has this right.
  • Upstream: go.dev/issue/58218 tracks worktree support for build stamping. Observed behavior on go1.26.4 says linked-worktree .git files are still not honored in our layouts; worth re-checking the issue/CL status before assuming a toolchain bump fixes anything.

Why this matters (repro builds / provenance)

We rely on embedded build metadata for reproducible-build verification and general provenance. Current state:

  • Safe: CI/release artifacts — full buildvcs stamps + ldflags Version/Revision. Attestation pipeline inputs unaffected.
  • Degraded: anything built in a worktree context ships without vcs.* BuildInfo. Scanners/tooling reading go version -m see no VCS fields. ldflags build.Revision is the only revision carried, and only for Makefile-driven builds (bare go build in a worktree container has neither).
  • Trap: host-side worktree builds (B) are silently unstamped today — no error, easy to miss.

Constraints on any fix

  • No blanket behavior changes: the mitigation must stay scoped to topologies where stamping is impossible (worktree containers). Host non-worktree and CI builds must keep full stamping. (Current opts.Worktree scoping satisfies this.)
  • No chown/safe.directory on the mounted main .git (rejected: footgun, and it would produce wrong stamps anyway per (C)).

Candidate follow-ups

  1. Document ldflags as the canonical metadata path for worktree builds and ensure every build entry point used inside worktree containers goes through the Makefile (or otherwise injects build.Revision), so no artifact is metadata-free.
  2. Track upstream go.dev/issue/58218; when go correctly anchors at the worktree's own gitdir, drop the GOFLAGS injection (it's overridable per-agent already, so opt-in trials are cheap).
  3. Consider a clawker-side guard/lint for the silent host case (B): warn when building from a clawker worktree on the host that binaries will be unstamped.
  4. Re-verify after any toolchain bump: the behavior matrix above is cheap to re-run (scripts below).

Repro

Worktree container (case D):

env GOFLAGS= go build -buildvcs=true ./cmd/gen-docs
# error obtaining VCS status: exit status 128
cd /Users/andrew/Code/clawker && git status --porcelain
# fatal: detected dubious ownership in repository at '/Users/andrew/Code/clawker'

Controlled matrix (any machine):

T=$(mktemp -d); cd $T
git init -q main; cd main; git commit -q --allow-empty -m x
mkdir -p cmd; printf 'package main\nfunc main(){}\n' > cmd/main.go; printf 'module probe\ngo 1.25\n' > go.mod
git add -A; git commit -q -m code

# (B) outside worktree — silently unstamped even when forced:
git worktree add -q $T/wt-out -b wt1 && cd $T/wt-out
go build -buildvcs=true -o /tmp/pB ./cmd && go version -m /tmp/pB | grep vcs  # no output

# (C) nested worktree — stamps the PARENT:
cd $T/main && git worktree add -q $T/main/sub/wt-in -b wt2 && cd $T/main/sub/wt-in
go build -buildvcs=true -o /tmp/pC ./cmd && go version -m /tmp/pC | grep vcs
# vcs.revision = parent HEAD, vcs.modified=true from the parent's dirty tree

Shimming git on PATH during (C)/(D) shows every VCS probe runs with cwd = the parent repo root, confirming go never anchors at the worktree's .git file.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions