Run golangci-lint only in docker, on every run (closes #55)
check / check (push) Successful in 1m23s

script/lint builds the new Dockerfile.lint, where golangci-lint runs as
a build step. The lint stage is rebuilt on every run, so an unchanged
tree is linted too; the module download stays cached. script/bootstrap
no longer installs golangci-lint. The Dockerfile lint stage calls
golangci-lint directly, since make lint now starts a docker build.
golangci-lint config verify is not run: it fetches its schema live over
unpinned HTTPS.

Model: opus-5-5
This commit is contained in:
2026-10-04 04:49:36 +00:00
committed by clawbot
parent 641d5659ec
commit 9d4259afa3
6 changed files with 49 additions and 14 deletions
+2 -1
View File
@@ -9,7 +9,8 @@ RUN go mod download
COPY . . COPY . .
RUN make fmt-check RUN make fmt-check
RUN make lint # Not make lint: script/lint is a docker build, which cannot run in here.
RUN golangci-lint run --config .golangci.yml ./...
# Build stage — tests and compilation # Build stage — tests and compilation
# golang 1.24.13-alpine (2026-03-10) # golang 1.24.13-alpine (2026-03-10)
+19
View File
@@ -0,0 +1,19 @@
# Lint image, built by script/lint: golangci-lint runs as a build step, so a
# successful build is a clean lint. Works where the docker daemon is remote
# and bind mounts are impossible.
# golangci/golangci-lint:v2.12.2 (Debian-based), 2026-08-07
FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS deps
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
# script/lint rebuilds this stage on every run, by this name; the module
# download above stays cached.
FROM deps AS lint
COPY . .
RUN golangci-lint run --config .golangci.yml ./...
+6 -3
View File
@@ -496,15 +496,18 @@ standard: normalized scripts in `script/` are the entrypoints for the
development workflow, and the Makefile targets are thin shims that call development workflow, and the Makefile targets are thin shims that call
them. We provide: them. We provide:
- `script/bootstrap` — install all dependencies (Go, golangci-lint, Go - `script/bootstrap` — install all dependencies (Go, Go module
module download), idempotently download), idempotently; golangci-lint is not installed, it runs in
docker
- `script/setup` — make a fresh clone ready for development: runs - `script/setup` — make a fresh clone ready for development: runs
`script/bootstrap`, then `script/install-precommit` `script/bootstrap`, then `script/install-precommit`
- `script/projectname` — output the project name (`secret`); used by - `script/projectname` — output the project name (`secret`); used by
other scripts such as `script/docker` other scripts such as `script/docker`
- `script/test` — run `go vet` and the test suite (verbose rerun on - `script/test` — run `go vet` and the test suite (verbose rerun on
failure) failure)
- `script/lint` — run `golangci-lint` - `script/lint` — run `golangci-lint` in docker only: builds
`Dockerfile.lint`, where the linter is a build step that runs on every
call, also on an unchanged tree
- `script/fmt` — format all Go code (writes) - `script/fmt` — format all Go code (writes)
- `script/fmt-check` — check formatting without writing - `script/fmt-check` — check formatting without writing
- `script/check` — run `script/test`, `script/lint`, and - `script/check` — run `script/test`, `script/lint`, and
+7
View File
@@ -25,6 +25,13 @@ Bring the repo into policy compliance in one commit:
# Completed Steps # Completed Steps
- 2026-10-04: Lint runs only in docker: `script/lint` builds
`Dockerfile.lint`, where golangci-lint is a build step rebuilt on
every run (`--no-cache-filter`), so an unchanged tree is linted too;
the module download stays cached. `script/bootstrap` no longer
installs golangci-lint, and the `Dockerfile` lint stage calls it
directly instead of `make lint`. `golangci-lint config verify` is not
run: it fetches its schema live over unpinned HTTPS.
- 2026-10-04: A PGP unlocker whose metadata has no usable GPG key ID - 2026-10-04: A PGP unlocker whose metadata has no usable GPG key ID
no longer panics: `GetID()` warns with the unlocker's directory and no longer panics: `GetID()` warns with the unlocker's directory and
returns `pgp-unknown`. `ListUnlockers` skips, with a warning, an returns `pgp-unknown`. `ListUnlockers` skips, with a warning, an
+1 -6
View File
@@ -6,6 +6,7 @@
# make, node, yarn, go, or python). Node is used directly if installed; # make, node, yarn, go, or python). Node is used directly if installed;
# otherwise a pinned version is installed via nvm (installing nvm # otherwise a pinned version is installed via nvm (installing nvm
# itself first, from a hash-verified release archive, never curl | sh). # itself first, from a hash-verified release archive, never curl | sh).
# golangci-lint is never installed: script/lint runs it in docker.
# #
# Uncomment the language sections in main() that apply to this repo. # Uncomment the language sections in main() that apply to this repo.
set -eu set -eu
@@ -136,12 +137,6 @@ main() {
# ---- Go repos ---- # ---- Go repos ----
if missing go; then pkg_install go golang go go; fi if missing go; then pkg_install go golang go go; fi
# golangci-lint: packaged in nix, brew, and apk. On apt there is no
# package: download a specific release archive from GitHub and
# verify its hash (verify_sha256), never curl | sh.
if missing golangci-lint; then
pkg_install golangci-lint golangci-lint golangci-lint golangci-lint
fi
go mod download go mod download
# ---- Python repos ---- # ---- Python repos ----
+14 -4
View File
@@ -1,14 +1,24 @@
#!/bin/sh #!/bin/sh
# script/lint: run the linter. # script/lint: run the linter, in docker only. Builds Dockerfile.lint,
# where golangci-lint runs as a build step.
#
# A cached build lints nothing, so --no-cache-filter rebuilds the lint
# stage on every run, an unchanged tree included. It ignores a stage name
# that does not exist, so --target names the same stage: a rename then
# fails the build instead of serving the lint from cache. cacheonly keeps
# no image; only the build's success matters.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
# CGO is required (Makefile exports this too) docker build \
export CGO_ENABLED=1 --progress=plain \
golangci-lint run --timeout 5m --target lint \
--no-cache-filter=lint \
--output=type=cacheonly \
-f Dockerfile.lint .
} }
main "$@" main "$@"