Run all linting in Docker via Dockerfile.lint + script/lint #20

Closed
opened 2026-08-10 13:16:52 +02:00 by clawbot · 2 comments
Collaborator

Owner ruling, sneak 2026-08-09: every lint run happens inside a Docker container, invoked through the script/ entrypoint. Docker is always available. Linting runs independently and does not need a cache. He has directed a PR for every repo not already set up this way.

Reference implementation is sneak/homoicon — copy its shape: a root Dockerfile.lint built FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240, which COPYs the repo in and runs golangci-lint run --config .golangci.yml ./... as a build step, with script/lint reduced to building it. Linting as a build step means a successful build IS a clean lint, and it works even where the docker daemon is remote and bind mounts are impossible.

Two things to get right, both of which would otherwise ship a false green:

  1. A cached build lints nothing. A lint build on an unchanged tree returns success in well under a second having run no linter. Caching is explicitly waived here, so force the lint layers to execute.
  2. golangci-lint config verify fetches its JSON schema over an unpinned live HTTPS call. Decide deliberately whether to include it.

Also remove golangci-lint installation from script/bootstrap — nothing runs on the host any more.

Note this repo carries STRTA on a next branch rather than main; land this consistently with whatever the branch state is when it is picked up.

Definition of done

  • script/lint runs the linter only in Docker; no host golangci-lint path remains.
  • Two consecutive script/lint runs on an unchanged tree both demonstrably execute the linter.
  • Negative control: introduce a deliberate lint violation, confirm it fails with that specific finding, revert, confirm clean.
  • make check still green.

Canonical tracking issue: sneak/prompts#40

Owner ruling, sneak 2026-08-09: every lint run happens inside a Docker container, invoked through the `script/` entrypoint. Docker is always available. Linting runs independently and does not need a cache. He has directed a PR for every repo not already set up this way. Reference implementation is `sneak/homoicon` — copy its shape: a root `Dockerfile.lint` built `FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240`, which COPYs the repo in and runs `golangci-lint run --config .golangci.yml ./...` as a build step, with `script/lint` reduced to building it. Linting as a build step means a successful build IS a clean lint, and it works even where the docker daemon is remote and bind mounts are impossible. Two things to get right, both of which would otherwise ship a false green: 1. **A cached build lints nothing.** A lint build on an unchanged tree returns success in well under a second having run no linter. Caching is explicitly waived here, so force the lint layers to execute. 2. **`golangci-lint config verify` fetches its JSON schema over an unpinned live HTTPS call.** Decide deliberately whether to include it. Also remove golangci-lint installation from `script/bootstrap` — nothing runs on the host any more. Note this repo carries STRTA on a `next` branch rather than `main`; land this consistently with whatever the branch state is when it is picked up. ## Definition of done - `script/lint` runs the linter only in Docker; no host golangci-lint path remains. - Two consecutive `script/lint` runs on an unchanged tree both demonstrably execute the linter. - Negative control: introduce a deliberate lint violation, confirm it fails with that specific finding, revert, confirm clean. - `make check` still green. Canonical tracking issue: https://git.eeqj.de/sneak/prompts/issues/40
Author
Collaborator

State, 2026-10-03 12:40 UTC: queued, not started. Waits on #16, which brings main (golangci-lint v2.12.2, .golangci.yml) into next; this unit branches from next after that lands. It may run in parallel with #22, and goes before #23, which edits the same build files. The Dockerfile currently runs make lint, so moving script/lint into its own docker build means the Dockerfile's lint step must call the linter directly. The issue body is the brief.

Model: opus-5-5

State, 2026-10-03 12:40 UTC: queued, not started. Waits on https://git.eeqj.de/sneak/simplelog/issues/16, which brings `main` (golangci-lint v2.12.2, `.golangci.yml`) into `next`; this unit branches from `next` after that lands. It may run in parallel with https://git.eeqj.de/sneak/simplelog/issues/22, and goes before https://git.eeqj.de/sneak/simplelog/issues/23, which edits the same build files. The `Dockerfile` currently runs `make lint`, so moving `script/lint` into its own docker build means the `Dockerfile`'s lint step must call the linter directly. The issue body is the brief. Model: opus-5-5
Author
Collaborator

Plan. Branches from next, which now has main's .golangci.yml` and lint image (#16 has landed).

  1. A root Dockerfile.lint: FROM the pinned v2.12.2 image named above (with the version-and-date comment REPO_POLICIES.md asks for), copy go.mod/go.sum, go mod download, copy the repo, RUN golangci-lint run --config .golangci.yml ./....
  2. script/lint only builds that file, with the build cache off (--no-cache), so every run executes the linter, as the waived cache allows. It leaves no image behind.
  3. golangci-lint config verify is left out. It downloads its schema from the network on every run, unpinned, so the result of the check would depend on something outside the repo. Disclosed in the PR.
  4. The Dockerfile lint stage runs golangci-lint run --config .golangci.yml ./... directly in place of make lint, because make lint now starts a docker build of its own. make fmt-check stays, since it is gofmt. The comment in script/cibuild claiming that the Dockerfile runs script/check is corrected to say what it actually runs (already wrong before this unit).
  5. script/fmt drops golangci-lint run --fix, and script/bootstrap stops installing golangci-lint. The README's Entrypoints section, and anything else that says the linter runs on the host, are brought in line.
  6. Only the shape the issue describes is copied from homoicon, not its extra checking scripts.

Model: opus-5-5

Plan. Branches from `next`, which now has `main's `.golangci.yml` and lint image (https://git.eeqj.de/sneak/simplelog/issues/16 has landed). 1. A root `Dockerfile.lint`: `FROM` the pinned v2.12.2 image named above (with the version-and-date comment `REPO_POLICIES.md` asks for), copy `go.mod`/`go.sum`, `go mod download`, copy the repo, `RUN golangci-lint run --config .golangci.yml ./...`. 2. `script/lint` only builds that file, with the build cache off (`--no-cache`), so every run executes the linter, as the waived cache allows. It leaves no image behind. 3. `golangci-lint config verify` is left out. It downloads its schema from the network on every run, unpinned, so the result of the check would depend on something outside the repo. Disclosed in the PR. 4. The `Dockerfile` lint stage runs `golangci-lint run --config .golangci.yml ./...` directly in place of `make lint`, because `make lint` now starts a docker build of its own. `make fmt-check` stays, since it is `gofmt`. The comment in `script/cibuild` claiming that the `Dockerfile` runs `script/check` is corrected to say what it actually runs (already wrong before this unit). 5. `script/fmt` drops `golangci-lint run --fix`, and `script/bootstrap` stops installing golangci-lint. The README's Entrypoints section, and anything else that says the linter runs on the host, are brought in line. 6. Only the shape the issue describes is copied from homoicon, not its extra checking scripts. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/simplelog#20