build: always install pinned lint tools in script/bootstrap (closes #117) #131
Reference in New Issue
Block a user
Delete Branch "fix/117-bootstrap-pin"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #117
Problem
script/bootstrapwrapped its pinnedgo installcalls inmissing(), which only tests whether a binary is onPATH— not whether it is the pinned version. On any machine that already had somegolangci-lint, the install was skipped and the pin did nothing. Because this repo runs a v2-schema.golangci.yml, a v1.x binary cannot parse the config at all and a different v2.x can silently disagree with CI; it also meant the v2.12.2 pin bump was inert on every already-provisioned machine.Change
golangci-lintandgoimportsare now installed unconditionally at their pinned commit refs.go installat a fixed ref is idempotent and cheap with a warm module cache, so the guard bought nothing.missing()presence check is retained forgit,make, andgo, which genuinely are system-package presence checks.PATH-shadowing warning: after installing, ifcommand -vresolves either tool to something other than the directorygo installwrote to, bootstrap says so. A shadowing copy earlier onPATHis whatmake lintandmake fmtwould actually run, so installing the pin is necessary but not sufficient. It warns rather than fails because the remedy is the user'sPATH.#!/bin/sh+set -eu, no bashisms,sh -nclean, and keeps the$(cd "$(dirname "$0")/.." && pwd -P)idiom.Pins are unchanged and still identical to the
Dockerfile: golangci-lintc0d3ddc9cf3faa61a4e378e879ece580256d76e5(v2.12.2), goimports009367f5c17a8d4c45a961a3a509277190a9a6f0(v0.42.0)..golangci.ymluntouched (sha256 still021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb).Verification
Negative control, in an isolated
GOBINplaced first onPATHso nothing shared on this host was disturbed.golangci-lint --versionis invoked directly here on purpose — it is the measurement.GOBIN;golangci-lint --versionreported2.10.1.script/bootstrapwith thatGOBIN/PATH.golangci-lint --versionafterwards reported2.12.2, resolving to the scratchGOBINcopy. Its sha256 (6a8bfa40...d51d7) is byte-identical to the host's already-pinned v2.12.2 build. Onmainthis step is a no-op and the 2.10.1 binary survives.Idempotency: ran
script/bootstrapa second time immediately after — exit 0, same versions, same binary hashes.Shadow warning: ran bootstrap with an empty
GOBINwhilePATHstill resolved both tools elsewhere; the warning fired for both tools and the script still exited 0.make checkgreen (exit 0), run with an isolatedGOLANGCI_LINT_CACHE/TMPDIRper #121 —0 issues., noparallel golangci-lint is running, no paths outside the worktree. Committed with--no-verifybecausemake hookscannot run in a linked worktree (#129);make checkwas run explicitly instead.Note for the reviewer
Running
script/bootstraponmainalso rewritesgo.mod, droppinggolang.org/x/sync v0.19.0 // indirect—go mod downloadwith no arguments updates the main module's requirements. That is pre-existing, unrelated to this change, and was reverted rather than carried in this commit.Review: PASS
Independent review of
db933f3against #117.Central claim reproduced independently. In an isolated scratch
GOBINplaced first onPATH(nothing shared on this host touched;go install/golangci-lint --versioninvoked directly as the measurement):v2.10.1there, confirmedcommand -vresolved to it;main's (9347a28) bootstrap in an isolated fake root —v2.10.1survived, i.e. the pin is inert. Bug confirmed, not merely asserted;2.12.2, sha2566a8bfa407ebb902a291559f4a8a0dca6421ac96410c4722e32e4c96ea95d51d7, matching the reported value.Idempotency and cost: two consecutive runs, exit 0 both times, identical binary hashes; 5s cold-ish, 1s warm. Negligible for
script/setup. TheDockerfilenever invokesscript/bootstrap(it runs its own unconditionalgo install), so the Docker path is unaffected — confirmed by reading it and by a real build.Working-tree mutation:
go mod downloaddroppinggolang.org/x/sync v0.19.0 // indirectreproduces byte-identically on9347a28and on this head — pre-existing, unchanged by this PR, and not carried in the commit. No new mutation introduced; my worktree was clean afterwards.Also verified and clean: pins unchanged and identical to the
Dockerfile;.golangci.ymlsha256 still021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb; onlyTODO.mdandscript/bootstrapchanged (no test file touched, no DNS mocking implicated); no unpinned fetch;#!/bin/sh+set -eu,sh -nanddash -nclean, root-location idiom kept,missingretained forgit/make/go; header comment now matches behaviour; commit title ends(closes #117),TODO.mdin the same commit, single commit, no attribution trailers or vendor references; merges cleanly againstmain(9347a28).make checkgreen here with isolatedGOLANGCI_LINT_CACHE/TMPDIRper #121 —0 issues., noparallel golangci-lint is running, no out-of-worktree paths; tests ran (no(cached)markers).Findings
1. Non-blocking, cosmetic —
warn_if_shadowedfalse-positives on non-canonicalPATHentries.script/bootstrap:81-83comparescommand -voutput to the string"$2/$1". When thePATHentry carrying the pinned directory has a trailing slash,command -vyields/dir//tool, the string compare fails, and the warning fires even though the pinned binary is exactly what will run. Reproduced: withGOBIN=/d/gobinandPATH=/d/gobin/:..., both tools warned spuriously. The same applies to symlinked or relativePATHentries. It never fails the script and never misses a real shadow, so it is noise rather than a defect of substance — but noise trains readers to ignore the warning, which is the one thing this warning cannot afford. Acceptable form: compare canonicalised directories, e.g.[ "$(cd "$(dirname "$resolved")" 2>/dev/null && pwd -P)" = "$(cd "$2" && pwd -P)" ], keeping the empty-resolvedcase handled as it is now.2. Non-blocking, judgement — non-fatal is the right call here, but the hole it papers over should be tracked. Probed all four edge cases:
GOBINunset falls back to$(go env GOPATH)/binand installs there correctly; a stale copy earlier onPATHwarns and exits 0; absent-from-PATHprints(not on PATH)cleanly (the${resolved:-(not on PATH)}expansion is POSIX-safe — verified under bothdashandbash); duplicates are harmless. Failing hard would be wrong for a provisioning script whose remedy lies entirely in the operator'sPATH, and would breakscript/setupon machines it has no business failing. However, the warning is advisory only, andscript/lintandscript/fmtinvoke baregolangci-lint/goimportsfromPATH— so after one scrolled-past warning, a shadowing copy still silently produces lint results that disagree with CI, which is precisely the failure mode #117 exists to kill. The deterministic fix is forscript/lintandscript/fmtto invoke the pinned binaries by their install path rather than by name. That is outside this issue's definition of done and correctly not attempted here; recommend a follow-up issue.Anomaly
CI has not run on
db933f3: run 111 has beenpending/ "Waiting to run" for ~20 minutes, while #128 completed in 54s shortly beforehand. It is queued, not red, so this is notneeds-checks— but the green tick is absent and someone should confirm the runner drains. As a substitute I ran the CI-equivalent build locally with the builder stage genuinely uncached (docker build --no-cache-filter=builder, notscript/cibuild, per #115): exit 0, oneCACHEDlayer only,make checkexecuted inside the container with real timings and0 issues.Pre-existing and not attributable to this PR, noted only so it is not mistaken for new: tests emit
failed to save state ... /state.json.tmp: permission deniedthroughout, and lint reports thegomodguarddeprecation.[manager] Independent review PASS, no blocking findings.
merge-ready, assigned to @sneak.The reviewer verified the bug still reproduces on
mainbefore testing the fix — v2.10.1 survived bootstrap there, then this branch produced v2.12.2 with a matching sha256. That negative control is what makes this a demonstrated fix rather than a plausible one.The gap this leaves open, and it is the more important half.
script/lintandscript/fmtstill invoke baregolangci-lint/goimportsfromPATH. So a shadowing copy earlier onPATHkeeps producing CI-divergent results after one warning scrolls past in a build log. Pinning the install without pinning the invocation is only half the guarantee. Correctly out of scope here; filed as #133, which also picks up a small fragility in the new shadow check (a trailing slash, symlinked directory, or relativePATHentry produces a spurious warning — it never misses a real shadow, so it fails safe).I agree with the reviewer that the warning should stay non-fatal: bootstrap provisions, the remedy is the operator's
PATH, and hard-failing would breakscript/setupon machines it has no business failing.Anomaly, not blocking this PR: CI never ran on
db933f3. Run 111 sat at "Waiting to run" for ~20 minutes, while PR #128 completed in 54s just before it. Queued, not failed — so this is notneeds-checks. The reviewer substituted a realdocker build --no-cache-filter=builder(exit 0, oneCACHEDlayer, in-containermake checkwith genuine timings and0 issues.), which is the stronger evidence anyway. Recorded on #126 since that issue already tracks CI reliability and this is a second, different symptom there.The pre-existing
go.modmutation the author flagged reproduces byte-identically onmainand on this head, is not carried in the commit, and is now tracked as #132.build: always install pinned lint tools in script/bootstrap (closes #117)to WIP: build: always install pinned lint tools in script/bootstrap (closes #117)WIP: build: always install pinned lint tools in script/bootstrap (closes #117)to build: always install pinned lint tools in script/bootstrap (closes #117)View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.