script/bootstrap:74 installs the pinned linter only when the command is absent:
if missing golangci-lint; then go install "$GOLANGCI_LINT_REF"; fi
missing() is ! command -v "$1". So on any machine that already has somegolangci-lint on PATH, GOLANGCI_LINT_REF is never consulted and the pin does nothing. Bumping the version in script/bootstrap — which is exactly what #3 just did — has no effect on such a machine, forever.
This is not hypothetical. Reproduced on the manager host immediately after #3 merged:
$ make check
ok sneak.berlin/go/sfdupes (cached) coverage: 72.8% of statements
0 issues.
$ golangci-lint --version
golangci-lint has version 2.10.1 built with go1.26.5
So make check reported green while running a linter two minor versions behind the pin, against a config written for a newer one. The Docker lint stage uses the digest-pinned image and is unaffected, which means local and CI can disagree silently — the worst possible failure mode for a gate, because the disagreement only shows up after a push.
Pre-existing since 3abeacf; surfaced by the independent review of PR #2.
Definition of done
script/bootstrap compares the installed version against the pin and reinstalls when it differs, rather than testing only for presence. Parse golangci-lint --version and match the pinned version exactly.
The pinned version lives in exactly one place in the script, so future bumps cannot half-apply.
The same presence-only pattern is audited for the other tools script/bootstrap installs (git, make, go); document deliberately unpinned system tools rather than silently leaving them.
Running script/bootstrap on a host with a wrong-version golangci-lint upgrades it, and a second run is a no-op.
make check and make docker green, and a local make lint uses the same linter version as the Dockerfile lint stage.
`script/bootstrap:74` installs the pinned linter only when the command is absent:
if missing golangci-lint; then go install "$GOLANGCI_LINT_REF"; fi
`missing()` is `! command -v "$1"`. So on any machine that already has *some* `golangci-lint` on `PATH`, `GOLANGCI_LINT_REF` is never consulted and the pin does nothing. Bumping the version in `script/bootstrap` — which is exactly what #3 just did — has no effect on such a machine, forever.
This is not hypothetical. Reproduced on the manager host immediately after #3 merged:
$ make check
ok sneak.berlin/go/sfdupes (cached) coverage: 72.8% of statements
0 issues.
$ golangci-lint --version
golangci-lint has version 2.10.1 built with go1.26.5
So `make check` reported green while running a linter two minor versions behind the pin, against a config written for a newer one. The Docker lint stage uses the digest-pinned image and is unaffected, which means local and CI can disagree silently — the worst possible failure mode for a gate, because the disagreement only shows up after a push.
Pre-existing since `3abeacf`; surfaced by the independent review of PR #2.
## Definition of done
1. `script/bootstrap` compares the installed version against the pin and reinstalls when it differs, rather than testing only for presence. Parse `golangci-lint --version` and match the pinned version exactly.
2. The pinned version lives in exactly one place in the script, so future bumps cannot half-apply.
3. The same presence-only pattern is audited for the other tools `script/bootstrap` installs (`git`, `make`, `go`); document deliberately unpinned system tools rather than silently leaving them.
4. Running `script/bootstrap` on a host with a wrong-version `golangci-lint` upgrades it, and a second run is a no-op.
5. `make check` and `make docker` green, and a local `make lint` uses the same linter version as the Dockerfile lint stage.
clawbot
added this to the 1.0.0 milestone 2026-08-09 03:58:15 +02:00
Implementation plan (branch bootstrap-version-check, from main at 076d822):
Split the pin into a single source of truth in script/bootstrap: GOLANGCI_LINT_VERSION="2.12.2" (no leading v, matching what the
binary prints), with GOLANGCI_LINT_REF derived from it as github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v$GOLANGCI_LINT_VERSION.
A future bump then edits exactly one string, and the install ref and
the comparison value cannot drift apart (DoD 2).
Add a golangci_lint_version() helper that returns the installed
version or the empty string when the tool is absent. It returns
early if command -v golangci-lint fails (so a missing binary is
just "no version" rather than an error under set -eu), otherwise it
parses golangci-lint --version with awk, taking the field after
the literal word version and stripping an optional leading v.
That handles both shapes seen in the wild: this host's golangci-lint has version 2.10.1 built with go1.26.5 from (unknown, modified: ?, mod sum: "...") on (unknown) and the pinned image's
version-plus-commit line.
Replace the presence test with a version test (DoD 1):
installed="$(golangci_lint_version)"
if [ "$installed" != "$GOLANGCI_LINT_VERSION" ]; then
go install "$GOLANGCI_LINT_REF"
fi
which covers absent, older and newer alike, and is a no-op on the
second run (DoD 4). It prints what it found and what it wants before
installing, so the upgrade is visible in the bootstrap output.
Audit the other installs (DoD 3): git, make and go come from
the host package manager and are deliberately unpinned — the repo
pins no system toolchain versions and go.mod governs the language
version. Rather than leave that ambiguous next to a tool that now is version-checked, add a comment above the three missing calls
stating that presence-only is intentional for system packages, and a
comment on the linter block stating why it is different (a stale
linter silently disagrees with the digest-pinned Dockerfile lint
stage, which is exactly the failure this issue is about). No system
package gets pinned.
POSIX sh throughout (#!/bin/sh, set -eu, no bashisms) since Alpine
has no bash; existing structure and comment style preserved.
Verification (DoD 4 and 5): this host currently has v2.10.1 against the
v2.12.2 pin, so it is a live reproduction — run script/bootstrap and
show the upgrade, run it again and show the no-op, report golangci-lint --version before and after, then run make check and make docker and confirm the local linter now matches the Dockerfile
lint stage. If v2.12.2 surfaces findings on main that v2.10.1 was
hiding, those get reported rather than fixed here — out of scope for
this issue. TODO.md gets a Completed Steps entry in the same commit.
Implementation plan (branch `bootstrap-version-check`, from `main` at
`076d822`):
1. Split the pin into a single source of truth in `script/bootstrap`:
`GOLANGCI_LINT_VERSION="2.12.2"` (no leading `v`, matching what the
binary prints), with `GOLANGCI_LINT_REF` derived from it as
`github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v$GOLANGCI_LINT_VERSION`.
A future bump then edits exactly one string, and the install ref and
the comparison value cannot drift apart (DoD 2).
2. Add a `golangci_lint_version()` helper that returns the installed
version or the empty string when the tool is absent. It returns
early if `command -v golangci-lint` fails (so a missing binary is
just "no version" rather than an error under `set -eu`), otherwise it
parses `golangci-lint --version` with `awk`, taking the field after
the literal word `version` and stripping an optional leading `v`.
That handles both shapes seen in the wild: this host's
`golangci-lint has version 2.10.1 built with go1.26.5 from (unknown,
modified: ?, mod sum: "...") on (unknown)` and the pinned image's
version-plus-commit line.
3. Replace the presence test with a version test (DoD 1):
installed="$(golangci_lint_version)"
if [ "$installed" != "$GOLANGCI_LINT_VERSION" ]; then
go install "$GOLANGCI_LINT_REF"
fi
which covers absent, older and newer alike, and is a no-op on the
second run (DoD 4). It prints what it found and what it wants before
installing, so the upgrade is visible in the bootstrap output.
4. Audit the other installs (DoD 3): `git`, `make` and `go` come from
the host package manager and are deliberately unpinned — the repo
pins no system toolchain versions and `go.mod` governs the language
version. Rather than leave that ambiguous next to a tool that now
*is* version-checked, add a comment above the three `missing` calls
stating that presence-only is intentional for system packages, and a
comment on the linter block stating why it is different (a stale
linter silently disagrees with the digest-pinned Dockerfile lint
stage, which is exactly the failure this issue is about). No system
package gets pinned.
POSIX `sh` throughout (`#!/bin/sh`, `set -eu`, no bashisms) since Alpine
has no bash; existing structure and comment style preserved.
Verification (DoD 4 and 5): this host currently has v2.10.1 against the
v2.12.2 pin, so it is a live reproduction — run `script/bootstrap` and
show the upgrade, run it again and show the no-op, report
`golangci-lint --version` before and after, then run `make check` and
`make docker` and confirm the local linter now matches the Dockerfile
lint stage. If v2.12.2 surfaces findings on `main` that v2.10.1 was
hiding, those get reported rather than fixed here — out of scope for
this issue. `TODO.md` gets a Completed Steps entry in the same commit.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
script/bootstrap:74installs the pinned linter only when the command is absent:missing()is! command -v "$1". So on any machine that already has somegolangci-lintonPATH,GOLANGCI_LINT_REFis never consulted and the pin does nothing. Bumping the version inscript/bootstrap— which is exactly what #3 just did — has no effect on such a machine, forever.This is not hypothetical. Reproduced on the manager host immediately after #3 merged:
So
make checkreported green while running a linter two minor versions behind the pin, against a config written for a newer one. The Docker lint stage uses the digest-pinned image and is unaffected, which means local and CI can disagree silently — the worst possible failure mode for a gate, because the disagreement only shows up after a push.Pre-existing since
3abeacf; surfaced by the independent review of PR #2.Definition of done
script/bootstrapcompares the installed version against the pin and reinstalls when it differs, rather than testing only for presence. Parsegolangci-lint --versionand match the pinned version exactly.script/bootstrapinstalls (git,make,go); document deliberately unpinned system tools rather than silently leaving them.script/bootstrapon a host with a wrong-versiongolangci-lintupgrades it, and a second run is a no-op.make checkandmake dockergreen, and a localmake lintuses the same linter version as the Dockerfile lint stage.Implementation plan (branch
bootstrap-version-check, frommainat076d822):Split the pin into a single source of truth in
script/bootstrap:GOLANGCI_LINT_VERSION="2.12.2"(no leadingv, matching what thebinary prints), with
GOLANGCI_LINT_REFderived from it asgithub.com/golangci/golangci-lint/v2/cmd/golangci-lint@v$GOLANGCI_LINT_VERSION.A future bump then edits exactly one string, and the install ref and
the comparison value cannot drift apart (DoD 2).
Add a
golangci_lint_version()helper that returns the installedversion or the empty string when the tool is absent. It returns
early if
command -v golangci-lintfails (so a missing binary isjust "no version" rather than an error under
set -eu), otherwise itparses
golangci-lint --versionwithawk, taking the field afterthe literal word
versionand stripping an optional leadingv.That handles both shapes seen in the wild: this host's
golangci-lint has version 2.10.1 built with go1.26.5 from (unknown, modified: ?, mod sum: "...") on (unknown)and the pinned image'sversion-plus-commit line.
Replace the presence test with a version test (DoD 1):
which covers absent, older and newer alike, and is a no-op on the
second run (DoD 4). It prints what it found and what it wants before
installing, so the upgrade is visible in the bootstrap output.
Audit the other installs (DoD 3):
git,makeandgocome fromthe host package manager and are deliberately unpinned — the repo
pins no system toolchain versions and
go.modgoverns the languageversion. Rather than leave that ambiguous next to a tool that now
is version-checked, add a comment above the three
missingcallsstating that presence-only is intentional for system packages, and a
comment on the linter block stating why it is different (a stale
linter silently disagrees with the digest-pinned Dockerfile lint
stage, which is exactly the failure this issue is about). No system
package gets pinned.
POSIX
shthroughout (#!/bin/sh,set -eu, no bashisms) since Alpinehas no bash; existing structure and comment style preserved.
Verification (DoD 4 and 5): this host currently has v2.10.1 against the
v2.12.2 pin, so it is a live reproduction — run
script/bootstrapandshow the upgrade, run it again and show the no-op, report
golangci-lint --versionbefore and after, then runmake checkandmake dockerand confirm the local linter now matches the Dockerfilelint stage. If v2.12.2 surfaces findings on
mainthat v2.10.1 washiding, those get reported rather than fixed here — out of scope for
this issue.
TODO.mdgets a Completed Steps entry in the same commit.