make lint calls script/lint, the only way golangci-lint is run, everywhere; it is never installed on the host.
Inside a container, script/lint runs golangci-lint run --config .golangci.yml ./.... Anywhere else it builds the hash-pinned Dockerfile.lint, whose last step runs script/lint again.
The Dockerfile lint stage runs make lint, the same entrypoint.
script/bootstrap no longer installs golangci-lint, and the nix-shell package lists in the Makefile and script/test drop it.
What a reader would trip over
Both Dockerfiles set ENV container=docker (the systemd convention for marking a container), and script/lint checks it. /.dockerenv cannot be used: build steps lack it, and hosts that are themselves containers have it.
script/lint passes a new CACHEBUST build-arg on every run, so the lint step is never served from cache; the layers above it still cache.
That step mounts a tmpfs over /root/.cache, so Go's and golangci-lint's caches are discarded, not stored in a layer no later run reuses. One make lint run adds under 1 MB of build cache.
Two consecutive make lint runs on an unchanged tree both ran the linter.
A planted errcheck violation failed both make lint and the Dockerfile lint stage with that finding.
The two Dockerfiles pin the lint image separately; change both pins together.
Disclosures
golangci-lint config verify is not run: it fetches its schema over an unpinned live HTTPS call, which REPO_POLICIES.md forbids.
Judgement call: script/lint always supplies a fresh CACHEBUST, so Dockerfile.lint does not refuse to build without one.
## What changed
`make lint` calls `script/lint`, the only way golangci-lint is run, everywhere; it is never installed on the host.
- Inside a container, `script/lint` runs `golangci-lint run --config .golangci.yml ./...`. Anywhere else it builds the hash-pinned `Dockerfile.lint`, whose last step runs `script/lint` again.
- The `Dockerfile` lint stage runs `make lint`, the same entrypoint.
- `script/bootstrap` no longer installs golangci-lint, and the nix-shell package lists in the `Makefile` and `script/test` drop it.
## What a reader would trip over
- Both Dockerfiles set `ENV container=docker` (the systemd convention for marking a container), and `script/lint` checks it. `/.dockerenv` cannot be used: build steps lack it, and hosts that are themselves containers have it.
- `script/lint` passes a new `CACHEBUST` build-arg on every run, so the lint step is never served from cache; the layers above it still cache.
- That step mounts a tmpfs over `/root/.cache`, so Go's and golangci-lint's caches are discarded, not stored in a layer no later run reuses. One `make lint` run adds under 1 MB of build cache.
- Two consecutive `make lint` runs on an unchanged tree both ran the linter.
- A planted `errcheck` violation failed both `make lint` and the `Dockerfile` lint stage with that finding.
- The two Dockerfiles pin the lint image separately; change both pins together.
## Disclosures
- `golangci-lint config verify` is not run: it fetches its schema over an unpinned live HTTPS call, which `REPO_POLICIES.md` forbids.
- Judgement call: `script/lint` always supplies a fresh `CACHEBUST`, so `Dockerfile.lint` does not refuse to build without one.
Model: opus-4-8 (implementation); opus-5-5 (rework)
PASS: linting runs only inside Docker through script/lint; both cache-defeat and negative-control traps hold (two consecutive runs on an unchanged tree each re-executed the linter, and a planted violation failed the run with its exact finding); every image is pinned by digest; the Dockerfile lint stage and make check lint with the identical command, config, and image; the config verify exclusion is stated; the build leaves no image behind; and the full Docker builder gate is green on a clean rebase onto current next.
Model: opus-4-8
PASS: linting runs only inside Docker through `script/lint`; both cache-defeat and negative-control traps hold (two consecutive runs on an unchanged tree each re-executed the linter, and a planted violation failed the run with its exact finding); every image is pinned by digest; the `Dockerfile` lint stage and `make check` lint with the identical command, config, and image; the `config verify` exclusion is stated; the build leaves no image behind; and the full Docker builder gate is green on a clean rebase onto current `next`.
Model: opus-4-8
Rebased onto the current next. The only conflict was in TODO.md Completed Steps: I kept the existing entry for #92 and placed this change's entry above it. Dockerfile merged cleanly — the lint-stage change here and the signing-key runtime change from #110 are in separate build stages and did not conflict. README.md needed no change: its script/lint description still reads correctly and the Getting Started signing-key text is untouched. The full build gate passed.
Model: opus-4-8
Rebased onto the current `next`. The only conflict was in `TODO.md` Completed Steps: I kept the existing entry for https://git.eeqj.de/sneak/pixa/issues/92 and placed this change's entry above it. `Dockerfile` merged cleanly — the lint-stage change here and the signing-key runtime change from https://git.eeqj.de/sneak/pixa/issues/110 are in separate build stages and did not conflict. `README.md` needed no change: its `script/lint` description still reads correctly and the Getting Started signing-key text is untouched. The full build gate passed.
Model: opus-4-8
Fix the bootstrap issue where it's installing an unneeded binary. You need to not be so sloppy about this. Obviously if we're doing linting in a container, we don't need to be installing linting tools on the host. Clean up after yourself. Remember that.
Fix the bootstrap issue where it's installing an unneeded binary. You need to not be so sloppy about this. Obviously if we're doing linting in a container, we don't need to be installing linting tools on the host. Clean up after yourself. Remember that.
Reworked the host-install finding: golangci-lint is no longer installed on the host and now lives only in the pinned container image.
Removed:
script/bootstrap: the golangci-lint version and sha256 pins, the release-archive download, the ensure/verify helpers, and the sole-use curl dependency.
golangci-lint from the nix-shell package lists in the Makefile and script/test, which build and test but never lint.
Rebased onto next.
Model: opus-4-8
Reworked the host-install finding: golangci-lint is no longer installed on the host and now lives only in the pinned container image.
Removed:
- `script/bootstrap`: the golangci-lint version and sha256 pins, the release-archive download, the ensure/verify helpers, and the sole-use curl dependency.
- `golangci-lint` from the nix-shell package lists in the `Makefile` and `script/test`, which build and test but never lint.
Rebased onto `next`.
Model: opus-4-8
PASS: golangci-lint now installs and runs only inside the pinned Docker image and nowhere on the host — script/bootstrap no longer fetches or installs it (its version/sha256 pins, the release download, the ensure/verify helpers, and the curl dependency they alone used are all gone, with no orphaned references and the build/test toolchain still installed), and golangci-lint is dropped from the nix-shell lists in the Makefile and script/test; Dockerfile.lint and the Dockerfile lint stage are pinned to the same digest and run the identical command and config, the cache-defeat trap holds (two consecutive script/lint runs on an unchanged tree each re-executed the linter and a planted violation failed with its exact finding), the full builder gate is green on a clean rebase onto current next, and the PR targets next with the closes keyword and correct model lines throughout — see #104
Model: opus-4-8
PASS: golangci-lint now installs and runs only inside the pinned Docker image and nowhere on the host — `script/bootstrap` no longer fetches or installs it (its version/sha256 pins, the release download, the ensure/verify helpers, and the curl dependency they alone used are all gone, with no orphaned references and the build/test toolchain still installed), and `golangci-lint` is dropped from the nix-shell lists in the `Makefile` and `script/test`; `Dockerfile.lint` and the `Dockerfile` lint stage are pinned to the same digest and run the identical command and config, the cache-defeat trap holds (two consecutive `script/lint` runs on an unchanged tree each re-executed the linter and a planted violation failed with its exact finding), the full builder gate is green on a clean rebase onto current `next`, and the PR targets `next` with the closes keyword and correct model lines throughout — see https://git.eeqj.de/sneak/pixa/issues/104
Model: opus-4-8
“make lint” should invoke strta lint, which should detect if it’s in docker or not and either build the lint container or run the linter. one entrypoint in all cases.
“make lint” should invoke strta lint, which should detect if it’s in docker or not and either build the lint container or run the linter. one entrypoint in all cases.
Rework plan for sneak's comment above (#122 (comment)), queued for the next free worker:
make lint calls script/lint, the only way the linter runs, everywhere.
script/lint detects whether it runs inside a container: inside, it runs golangci-lint directly; outside, it builds Dockerfile.lint, whose lint step calls script/lint again.
The Dockerfile lint stage goes back to RUN make lint.
Kept from before: no linter on the host, the lint step never served from cache, config verify excluded.
Model: opus-5-5
Rework plan for sneak's comment above (https://git.eeqj.de/sneak/pixa/pulls/122#issuecomment-98418), queued for the next free worker:
- `make lint` calls `script/lint`, the only way the linter runs, everywhere.
- `script/lint` detects whether it runs inside a container: inside, it runs `golangci-lint` directly; outside, it builds `Dockerfile.lint`, whose lint step calls `script/lint` again.
- The `Dockerfile` lint stage goes back to `RUN make lint`.
- Kept from before: no linter on the host, the lint step never served from cache, `config verify` excluded.
Model: opus-5-5
clawbot
changed title from build: run all linting in Docker via Dockerfile.lint (closes #104) to build: run all linting in Docker through script/lint (closes #104)2026-09-28 13:13:47 +02:00
Reworked per #122 (comment): make lint calls script/lint, which runs golangci-lint when inside a container (both Dockerfiles set container=docker) and otherwise builds Dockerfile.lint, whose last step runs script/lint again; the Dockerfile lint stage is back to make lint. Rebased onto next and squashed to one commit.
Model: opus-5-5
Reworked per https://git.eeqj.de/sneak/pixa/pulls/122#issuecomment-98418: `make lint` calls `script/lint`, which runs golangci-lint when inside a container (both Dockerfiles set `container=docker`) and otherwise builds `Dockerfile.lint`, whose last step runs `script/lint` again; the `Dockerfile` lint stage is back to `make lint`. Rebased onto `next` and squashed to one commit.
Model: opus-5-5
Dockerfile.lint, the final RUN script/lint: the step writes Go's build cache and golangci-lint's cache (about 700 MB for this tree) into its layer, and because CACHEBUST changes on every run, each make lint (every pre-commit run included) leaves a new build-cache entry of that size that no later run can ever reuse. Acceptable: keep those caches (/root/.cache) out of the layer, for example with a tmpfs or cache mount on that RUN, so a lint run leaves nothing behind.
Dockerfile.lint header comment: "Pinned to the same image as the Dockerfile lint stage, so the two cannot drift to different linter versions" promises something nothing enforces; the two digests are separate pins and drift as soon as one is bumped alone. Acceptable: say that both pins must be changed together.
Model: opus-5-5
FAIL
- `Dockerfile.lint`, the final `RUN script/lint`: the step writes Go's build cache and golangci-lint's cache (about 700 MB for this tree) into its layer, and because `CACHEBUST` changes on every run, each `make lint` (every pre-commit run included) leaves a new build-cache entry of that size that no later run can ever reuse. Acceptable: keep those caches (`/root/.cache`) out of the layer, for example with a tmpfs or cache mount on that `RUN`, so a lint run leaves nothing behind.
- `Dockerfile.lint` header comment: "Pinned to the same image as the Dockerfile lint stage, so the two cannot drift to different linter versions" promises something nothing enforces; the two digests are separate pins and drift as soon as one is bumped alone. Acceptable: say that both pins must be changed together.
Model: opus-5-5
Build cache: the lint step in Dockerfile.lint now mounts a tmpfs over /root/.cache; one make lint run adds under 1 MB.
Pin comment: now says to change both pins together.
Model: opus-5-5
Reworked against https://git.eeqj.de/sneak/pixa/pulls/122#issuecomment-103526, rebased onto `next`:
- Build cache: the lint step in `Dockerfile.lint` now mounts a tmpfs over `/root/.cache`; one `make lint` run adds under 1 MB.
- Pin comment: now says to change both pins together.
Model: opus-5-5
script/bootstrap, header comment: "golangci-lint runs only inside Dockerfile.lint" is not true; the Dockerfile lint stage runs it too (RUN make lint). Acceptable: say it runs only inside a container (Dockerfile.lint or the Dockerfile lint stage), never on the host.
Dockerfile, lint-stage pin comment (line 2): only Dockerfile.lint says the two pins must change together. Whoever bumps the Dockerfile pin is not pointed to Dockerfile.lint, so make lint and the image build end up on different linter versions. Acceptable: the same one-line note beside the Dockerfile pin, naming Dockerfile.lint.
Model: opus-5-5
FAIL
- `script/bootstrap`, header comment: "golangci-lint runs only inside Dockerfile.lint" is not true; the `Dockerfile` lint stage runs it too (`RUN make lint`). Acceptable: say it runs only inside a container (`Dockerfile.lint` or the `Dockerfile` lint stage), never on the host.
- `Dockerfile`, lint-stage pin comment (line 2): only `Dockerfile.lint` says the two pins must change together. Whoever bumps the `Dockerfile` pin is not pointed to `Dockerfile.lint`, so `make lint` and the image build end up on different linter versions. Acceptable: the same one-line note beside the `Dockerfile` pin, naming `Dockerfile.lint`.
Model: opus-5-5
make lint calls script/lint, the only way golangci-lint is run. Inside a
container it runs the linter; anywhere else it builds Dockerfile.lint,
whose last step runs script/lint again. Both Dockerfiles set
container=docker to mark the container, since /.dockerenv is missing in
build steps and present on hosts that are themselves containers. The
Dockerfile lint stage runs make lint.
A new CACHEBUST build-arg on every run keeps the lint step from being
served from cache; a tmpfs mount keeps Go's and golangci-lint's caches
out of that step's layer, so runs do not pile up build cache.
script/bootstrap and the nix-shell package lists no longer carry
golangci-lint. golangci-lint config verify is not run: it fetches its
schema over an unpinned live HTTPS call.
Model: opus-4-8 (implementation); opus-5-5 (rework)
script/bootstrap header: golangci-lint runs only inside a container, Dockerfile.lint or the Dockerfile lint stage.
Dockerfile lint-stage pin: now names Dockerfile.lint and says to change both pins together.
Model: opus-5-5
Reworked against https://git.eeqj.de/sneak/pixa/pulls/122#issuecomment-103585, rebased onto `next`:
- `script/bootstrap` header: golangci-lint runs only inside a container, `Dockerfile.lint` or the `Dockerfile` lint stage.
- `Dockerfile` lint-stage pin: now names `Dockerfile.lint` and says to change both pins together.
Model: opus-5-5
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.
What changed
make lintcallsscript/lint, the only way golangci-lint is run, everywhere; it is never installed on the host.script/lintrunsgolangci-lint run --config .golangci.yml ./.... Anywhere else it builds the hash-pinnedDockerfile.lint, whose last step runsscript/lintagain.Dockerfilelint stage runsmake lint, the same entrypoint.script/bootstrapno longer installs golangci-lint, and the nix-shell package lists in theMakefileandscript/testdrop it.What a reader would trip over
ENV container=docker(the systemd convention for marking a container), andscript/lintchecks it./.dockerenvcannot be used: build steps lack it, and hosts that are themselves containers have it.script/lintpasses a newCACHEBUSTbuild-arg on every run, so the lint step is never served from cache; the layers above it still cache./root/.cache, so Go's and golangci-lint's caches are discarded, not stored in a layer no later run reuses. Onemake lintrun adds under 1 MB of build cache.make lintruns on an unchanged tree both ran the linter.errcheckviolation failed bothmake lintand theDockerfilelint stage with that finding.Disclosures
golangci-lint config verifyis not run: it fetches its schema over an unpinned live HTTPS call, whichREPO_POLICIES.mdforbids.script/lintalways supplies a freshCACHEBUST, soDockerfile.lintdoes not refuse to build without one.Model: opus-4-8 (implementation); opus-5-5 (rework)
PASS: linting runs only inside Docker through
script/lint; both cache-defeat and negative-control traps hold (two consecutive runs on an unchanged tree each re-executed the linter, and a planted violation failed the run with its exact finding); every image is pinned by digest; theDockerfilelint stage andmake checklint with the identical command, config, and image; theconfig verifyexclusion is stated; the build leaves no image behind; and the full Docker builder gate is green on a clean rebase onto currentnext.Model: opus-4-8
66bf9a589dto8dc46af6b7Rebased onto the current
next. The only conflict was inTODO.mdCompleted Steps: I kept the existing entry for #92 and placed this change's entry above it.Dockerfilemerged cleanly — the lint-stage change here and the signing-key runtime change from #110 are in separate build stages and did not conflict.README.mdneeded no change: itsscript/lintdescription still reads correctly and the Getting Started signing-key text is untouched. The full build gate passed.Model: opus-4-8
Fix the bootstrap issue where it's installing an unneeded binary. You need to not be so sloppy about this. Obviously if we're doing linting in a container, we don't need to be installing linting tools on the host. Clean up after yourself. Remember that.
8dc46af6b7tof1c1bffb5aReworked the host-install finding: golangci-lint is no longer installed on the host and now lives only in the pinned container image.
Removed:
script/bootstrap: the golangci-lint version and sha256 pins, the release-archive download, the ensure/verify helpers, and the sole-use curl dependency.golangci-lintfrom the nix-shell package lists in theMakefileandscript/test, which build and test but never lint.Rebased onto
next.Model: opus-4-8
PASS: golangci-lint now installs and runs only inside the pinned Docker image and nowhere on the host —
script/bootstrapno longer fetches or installs it (its version/sha256 pins, the release download, the ensure/verify helpers, and the curl dependency they alone used are all gone, with no orphaned references and the build/test toolchain still installed), andgolangci-lintis dropped from the nix-shell lists in theMakefileandscript/test;Dockerfile.lintand theDockerfilelint stage are pinned to the same digest and run the identical command and config, the cache-defeat trap holds (two consecutivescript/lintruns on an unchanged tree each re-executed the linter and a planted violation failed with its exact finding), the full builder gate is green on a clean rebase onto currentnext, and the PR targetsnextwith the closes keyword and correct model lines throughout — see #104Model: opus-4-8
“make lint” should invoke strta lint, which should detect if it’s in docker or not and either build the lint container or run the linter. one entrypoint in all cases.
Rework plan for sneak's comment above (#122 (comment)), queued for the next free worker:
make lintcallsscript/lint, the only way the linter runs, everywhere.script/lintdetects whether it runs inside a container: inside, it runsgolangci-lintdirectly; outside, it buildsDockerfile.lint, whose lint step callsscript/lintagain.Dockerfilelint stage goes back toRUN make lint.config verifyexcluded.Model: opus-5-5
f1c1bffb5atod3f277d3f0build: run all linting in Docker via Dockerfile.lint (closes #104)to build: run all linting in Docker through script/lint (closes #104)Reworked per #122 (comment):
make lintcallsscript/lint, which runs golangci-lint when inside a container (both Dockerfiles setcontainer=docker) and otherwise buildsDockerfile.lint, whose last step runsscript/lintagain; theDockerfilelint stage is back tomake lint. Rebased ontonextand squashed to one commit.Model: opus-5-5
FAIL
Dockerfile.lint, the finalRUN script/lint: the step writes Go's build cache and golangci-lint's cache (about 700 MB for this tree) into its layer, and becauseCACHEBUSTchanges on every run, eachmake lint(every pre-commit run included) leaves a new build-cache entry of that size that no later run can ever reuse. Acceptable: keep those caches (/root/.cache) out of the layer, for example with a tmpfs or cache mount on thatRUN, so a lint run leaves nothing behind.Dockerfile.lintheader comment: "Pinned to the same image as the Dockerfile lint stage, so the two cannot drift to different linter versions" promises something nothing enforces; the two digests are separate pins and drift as soon as one is bumped alone. Acceptable: say that both pins must be changed together.Model: opus-5-5
d3f277d3f0to38fae4295bReworked against #122 (comment), rebased onto
next:Dockerfile.lintnow mounts a tmpfs over/root/.cache; onemake lintrun adds under 1 MB.Model: opus-5-5
38fae4295btod548cb7a17FAIL
script/bootstrap, header comment: "golangci-lint runs only inside Dockerfile.lint" is not true; theDockerfilelint stage runs it too (RUN make lint). Acceptable: say it runs only inside a container (Dockerfile.lintor theDockerfilelint stage), never on the host.Dockerfile, lint-stage pin comment (line 2): onlyDockerfile.lintsays the two pins must change together. Whoever bumps theDockerfilepin is not pointed toDockerfile.lint, somake lintand the image build end up on different linter versions. Acceptable: the same one-line note beside theDockerfilepin, namingDockerfile.lint.Model: opus-5-5
d548cb7a17to643a5ffd74Reworked against #122 (comment), rebased onto
next:script/bootstrapheader: golangci-lint runs only inside a container,Dockerfile.lintor theDockerfilelint stage.Dockerfilelint-stage pin: now namesDockerfile.lintand says to change both pins together.Model: opus-5-5
PASS: both comment findings are fixed, and
make lintnow goes throughscript/linteverywhere, including both Dockerfiles.Model: opus-5-5