Run all linting in Docker via Dockerfile.lint (closes #109) #165
Reference in New Issue
Block a user
Delete Branch "issue-109-docker-lint"
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 #109.
golangci-lint no longer runs on the host.
script/lintbuildsDockerfile.lint, which copies the repo into the digest-pinned golangci-lint image and lints as a build step, so a successful build is a clean lint.Changes
Dockerfile.lint(new). Two stages offgolangci/golangci-lint:v2.12.2@sha256:5cceeef0...: a cacheabledepsstage (go mod download) and alintstage that copies the tree and runs the linter.script/lintis now a POSIX-sh wrapper arounddocker build -f Dockerfile.lint --no-cache-filter=lint --progress=plain --output=type=cacheonly ., which additionally asserts that the linter's own summary line appears in the build output (see below).Dockerfilelint stage invokesgolangci-lintdirectly instead ofmake lint— it is already the pinned linter image, andmake lintwould now need a docker daemon inside the build.script/bootstrapno longer installs golangci-lint; the version pins, sha256 constants, release-archive download and the now-unusedverify_sha256helper are gone. It warns if docker is absent. Itscurlguard andscript/fetch-assetscall are untouched.README.md: new Linting section, corrected prerequisites, and the Docker stage list fixed (it described a two-stage build that installed golangci-lint in the builder, which has not been true for a while).TODO.mduntouched.Rebased onto
next@41ff16aClean, no conflicts. #146, #152 and #157 landed since the previous head; none overlaps this PR's hunks. Verified rather than assumed:
Dockerfile: the only delta from the previous headcc5836dis 146's builderapt-getline gainingjq, taken verbatim. This PR's ownDockerfiledelta againstnextis still exactly the lint-stage change.README.md: 152 added thescript/ci-mark-supersededEntrypoints bullet and rewrote the CI-gate-honesty tail; 146 rewrote the access-log region; 157 touched no Markdown at all. All are outside this PR's hunks.git diff origin/next HEAD -- README.mdstill touches only lint-related lines, so no #151 correction is clobbered.internal/handlers,internal/serverandtemplates/, so it cannot interact with this change. Rebased onto it and re-ran the full gate anyway; the numbers below are from the final head.script/checkreally runs test, lint, fmt-check in that order; the Makefile really has 16 targets of which 10 shimscript/, withbuild,run,dev,deps,clean,cssinline;script/ci-mark-supersededcorrectly has no Makefile target and the README does not claim one.The four non-
README.mdfiles are byte-identical to the twice-reviewed head, modulonext's ownjqline above.git rev-parse HEAD:<path>at this head:Dockerfile.lint9a9af51,script/bootstrap49001f8,script/lint15de890— the same blobs the third review recorded.Earlier rebase (README merge against #151), unchanged
README.mdwas the only conflicted file. 151 was a wholesale README accuracy rewrite that landed four minutes after the then-head was pushed; one of its new statements is made false by this PR, so the resolution was neither "take ours" nor "take theirs".golangci-lint v2.12.2 ... make bootstrap installs itbullet, which this PR falsifies, and folded linting into its Docker bullet:Docker (for linting, for the test stage of the CI gate, and for containerized deployment). 151'sGo 1.26.1+bullet and itscurl/script/fetch-assetsbullet are kept verbatim. This branch's "golangci-lint is not a prerequisite and must not be installed on the host" paragraph is kept, extended to namescript/bootstrapexplicitly.make fmt-checkline and this branch'smake lint # Run golangci-lint in Docker (Dockerfile.lint).Dockerfile.lintline is re-inserted.Dockerfileactually runs —make fmt-check, thengolangci-lint config verifyandgolangci-lint run, both with--network=none. This branch's paragraph explaining why the stage does not callmake lintis kept.Statements that auto-merged cleanly out of 151 but were left false by this PR are corrected in the same commit rather than knowingly shipped wrong: the Quick Start comment claiming
make bootstrapinstalls "the pinned linter"; the paragraph sayingscript/lintruns "whatevergolangci-lintis on the host"; and the CI-gate-honesty bullet listingmake lintamong what the image really runs.The
script/bootstrapconflict from the first rebase is folded in and unchanged: #164'sif missing curl; then pkg_install curl curl curl curl; fiand"$ROOT/script/fetch-assets"both survive;verify_sha256,install_golangci_lint_releaseandensure_golangci_lintare gone, withdetect_pkgmgrstill reached viapkg_install.README corrections in this round
Two statements the previous round left standing, both raised in #165 (comment). No code changed.
1. The CI-gate paragraph counted four check targets. Verified against the
Dockerfile: the image runsmake fmt-check,golangci-lint config verify,golangci-lint run,make test,make build— threemaketargets plus a direct binary invocation, not four targets. The count is dropped:>
script/cibuild—docker build .— is the CI gate: the checks run inside the image, so a build that succeeds is a repo that is formatted, linted, tested and compiled.2. The Linting section undercounted its own guarantees. "Two properties are load-bearing" is now "Three", with the summary-line assertion added as a bullet next to the flag it guards:
> -
script/lintdoes not trust that flag. Docker silently ignores--no-cache-filterfor a stage name that does not match, so a stage rename or a one-character typo would restore the cached false green with no warning and a fast exit 0. The script therefore tees the build output and treats a run as a pass only if golangci-lint's own summary line (N issues./N issues:) appears in it: no summary, no lint, whatever the exit code says.Gate at
1076edbmake check:Disclosure: this run had 10
(cached)test lines. It is the secondmake checkin this clone — the first, before the rebase onto 157, ran all 14 packages for real with zero cached lines (exit 0, 1m9.456s,#11 47.01 0 issues.), and the rebase only invalidated the packages 157 touched, which are exactly the three that re-executed. The image gate below re-runs everything with an empty cache.Full image gate,
docker build --no-cache-filter=lint --no-cache-filter=builder --progress=plain --output=type=cacheonly .:All 13 test packages ran with real durations; the graph executed through the final stage.
Two consecutive
make lintruns on an unchanged tree,git status --porcelainidentical between them, both really executing:Misspelled stage (
lnit), the two-run form — run 1 executes for real because editingscript/lintinvalidatesCOPY . ., and only run 2 can hit the cache:The false green reproduced and caught.
script/lintrestored afterwards and confirmed back to blob15de890.make bootstrapend to end in the fresh clone: exit 0,fetch-assets: installed static/js/alpine.min.js (3ed1eed2...),all assets in static/vendor.sha256 verified.Nothing tagged,
docker ps -aempty, no prune of any kind run.Trap 1: a cached build lints nothing
Without the flag, on an unchanged tree:
0.27s, exit 0, no linter. That is the false green.
--no-cache-filter=lintforces the lint stage to re-execute whiledepskeeps its cache.--output=type=cacheonlymeans no image is produced, so repeated local linting leaves nothing to clean up on a shared host.Trap 1a:
--no-cache-filtersilently ignores an unmatched stage nameRaised in review. Docker does not validate the argument, so the whole anti-false-green guarantee rested on the literal string
lintmatching the stage name, with a one-character typo or a stage rename silently restoring the cached false green — and failing fast and green, the worst shape a failure can take.script/lintno longer trusts the flag. It tees the build output and fails unless golangci-lint's own summary line (N issues./N issues:) appears in it. The build's exit status travels via a file, because a pipeline's status istee's and POSIX sh has nopipefail; output still streams live, so--progress=plainis not defeated. Re-verified at this head, above.Negative control
Deliberate violation added to
internal/globals/globals.go:make lint:The finding reaches the terminal in full — the tee captures a copy, it does not swallow the stream. Reverted, run again:
0 issues., exit 0.Note the reported path:
internal/globals/globals.go, relative to the repo root inside the container. The container holds only this repo, so the../other-worktree/...contamination from #106 is structurally impossible here rather than filtered after the fact.Trap 2:
config verifyand its live schema fetchI planned to drop
config verifyon hash-pinning grounds. Testing reversed that on both halves of the reasoning.Its value is real.
golangci-lint runsilently ignores config keys it does not recognize — a bogus top-level key, and a bogus key nested underrun:, both give0 issues.and exit 0.config verifyis the only thing that catches them:The cost is not real in this pinned image. The same detection works with the network fully removed (
net=none,rc=3on the bad config,rc=0on the good one). v2.12.2 resolves the schema from inside the image when the config'sversionmatches the binary, so it is content-addressed and hash-pinning holds.Decision: keep
config verify, and enforce the absence of a fetch rather than trust it. Both lint steps useRUN --network=none, which the built-in BuildKit frontend supports with no# syntaxdirective (so no extra external reference to pin). If a future image bump makes either step reach for the network, the build fails loudly instead of quietly acquiring an unpinned dependency. The reasoning is recorded in theDockerfile.lintheader so the step is not "helpfully" removed or the flag dropped.This is a deliberate divergence from
sneak/homoicon, which runsconfig verifywithout--network=noneand builds without--no-cache-filter.make checkwith no golangci-lint onPATHThe host has three copies, so an earlier run used a stripped environment that excludes all of them:
with the lint stage really executing inside it (
#11 51.69 0 issues.) and zero(cached)test lines.Notes
Dockerfile.lintis the place its linter stage goes. Not attempted here.The linter 'gomodguard' is deprecated (since v2.12.0) ... Replaced by gomodguard_v2..golangci.ymlis the standardized file that must only be updated from the canonical copy, so this belongs upstream rather than in this PR.REPO_POLICIES.mdstill describes the lint stage as part of the mainDockerfileonly. It is a synced cross-project document, so updating it is the canonical rollout's job, not this repo's.FAIL — needs-rebase
Content review passes: the central claim is real and I reproduced every load-bearing item independently in my own clone at
46248a2. The only blocker is that the branch no longer merges intonext.Blocker
1. Conflicts with
next@c378690(#164 squash-merged since this branch was cut).git merge-treereports one conflict,script/bootstrap;DockerfileandREADME.mdauto-merge. Resolution is mechanical but has one trap:script/fetch-assets; this PR rewrote the adjacent golangci-lint sentence. Keep both edits.main(): 164 addedif missing curl; then pkg_install curl curl curl curl; fi+"$ROOT/script/fetch-assets"immediately aftergo mod download; this PR deleted the adjacentensure_golangci_lintcall. Keep 164's two lines and this PR's deletion.pkg_install curlin the pre-164 file lived insideinstall_golangci_lint_release, which this PR deletes. 164's own curl guard must survive the merge orscript/fetch-assetsloses its fetcher on a bare host.script/fetch-assetsentirely (zero references in its base and its head), so it has not removed or broken the call — the merge must re-introduce it.Should fix during the rework
2.
--no-cache-filtersilently ignores a stage name that does not match. The whole anti-false-green guarantee rests on the literal stringlintinscript/lintmatching the stage name inDockerfile.lint, and Docker does not validate it. Verified on this host:A stage rename or a one-character typo therefore restores exactly the defect this PR exists to eliminate, with no error and a fast green.
Dockerfile.lint's "Do not drop that flag" comment does not cover this case. Acceptable fix: makescript/lintassert on evidence instead of trusting the flag — capture the build output (--progress=plainwrites to stderr) and exit non-zero unless the linter's own summary line (issues.) appears in it. Three lines of POSIX sh, and it converts the guarantee from a convention into a check.Note, not a defect
3.
REPO_POLICIES.mdstill states the Dockerfile lint stage "runsmake fmt-checkandmake lint". The mainDockerfilehere now callsgolangci-lintdirectly, which is correct (callingmake lintwould need a docker daemon inside the build), and the PR body discloses it. It does leave this repo textually out of step with the synced policy doc until the canonical rollout lands — track under sneak/prompts#40.Gate evidence (my clone, PR head
46248a2)--no-cache-filter,real 0m0.262s, exit 0, lint layersCACHED. With it, two consecutive runs on an unchanged tree: 62.7s and 52.7s,0 issues.printed both times,deps/go mod downloadstayingCACHED.os.Setenvproducederrcheckandrevivefindings,3 issues:, exit 1, paths relative to the repo root inside the container. Reverted, clean.--output=type=cacheonlydoes not mask the failure.golangci-lint runsilently ignores unknown config keys: confirmed. Bogus top-level key and bogus key nested underrun:both give0 issues./ exit 0, whileconfig verifyrejects both by jsonschema. Keepingverifyis justified.RUN --network=noneworks on the built-in BuildKit frontend with no# syntaxdirective (docker 29.7.2 / buildx 0.36.1), so no new unpinned reference. Enforcement proven: same image,getent hosts deb.debian.orgresolves under the default network and fails under--network=none.sha256:5cceeef0...resolves togolangci-lint has version 2.12.2. No other external reference introduced.make checkexit 0 withgolangci-lint: NOT ON PATHunderenv -i PATH="/usr/local/go/bin:/usr/bin:/bin"; zero(cached)test lines; lint executed in-container.docker build --no-cache-filter=lint --no-cache-filter=builder .exit 0 in 2m57s, zero(cached)test lines, lint stage genuinely re-executed.make bootstrapexit 0; no dead code left behind (verify_sha256,install_golangci_lint_release,ensure_golangci_lintall removed,detect_pkgmgrstill reached viapkg_install).46248a2(check / check, 3m9s). One commit, title ends(closes #109), basenext,TODO.mduntouched, no Claude/Anthropic references or attribution trailers,script/lintis POSIX sh withset -euand the canonical root idiom,make fmt-checkclean, inclusive terminology clean, #120 correctly not attempted.Interaction check for the rebase worker, so nobody "helpfully" tightens it: 164's
RUN script/fetch-assetssits in the builder stage, afterCOPY . .and beforemake test, with no network restriction. It is not in any stage this PR marks--network=none, and it must not be — that step needs the network.Dockerfile.lintneeds no change for 164:static/static.goembeds directories (//go:embed css js), not the individual file, so the package still type-checks withstatic/js/alpine.min.jsabsent from the context, andstatic/vendor_test.goonly needs the asset at run time, in the builder stage.Containers and images from this review: none left behind.
46248a2838to93be4d2846FAIL — needs-rebase
Scoped confirmation pass on the rework at
93be4d2, not a full re-review. Both reworked items pass in my own fresh clone. I relied on the prior review (#165 (comment)) for the digest resolving to v2.12.2, the--network=noneenforcement proof,make checkwith no host golangci-lint onPATH, and theconfig verifyreversal (thatgolangci-lint runsilently ignores unrecognized config keys), and did not redo those.Blocker
Conflicts with current
next@39064a3. #151 landed onnextat 23:44:59, four minutes after this head was pushed.git merge-tree HEAD origin/nextreports a conflict inREADME.md— four hunks, and this one is semantic rather than mechanical:nextnow says "golangci-lint v2.12.2 (the version pinned inscript/bootstrapand in theDockerfile's lint stage;make bootstrapinstalls it)". That statement is made false by this PR and must be replaced by this branch's version, whilenext's two other new bullets (Go 1.26.1+ wording, thecurl/script/fetch-assetsbullet) must be kept.next's newmake fmt-checkline and this branch'smake lint # Run golangci-lint in Docker (Dockerfile.lint).next's rewritten tree wholesale and re-insert only theDockerfile.lintline. Notenextalready renamed theDockerfilecomment to "Three stages: lint, test+build, Alpine runtime".nextrewrote it, this branch rewrote it, both edits are wanted.Nothing else conflicts. No content change is required.
Item 1 —
script/bootstrapconflict resolution: correctif missing curl; then pkg_install curl curl curl curl; fiand"$ROOT/script/fetch-assets"both survive at lines 74-75.make bootstrapin a bare clone: exit 0, Alpine.js fetched and verified againststatic/vendor.sha256.verify_sha256,install_golangci_lint_releaseandensure_golangci_lintare all gone;detect_pkgmgris still reached viapkg_install.dash -nclean on bothscript/bootstrapandscript/lint.Item 2 —
script/lintsummary assertion: correct, and I could not fool itlnit): FAILS loudly both runs — 0.31s / 0.24s,makeexit 2, full diagnostic. Note it failed on the first run too, not only the second: the author's "first run is a cache miss" trap did not reproduce for me because BuildKit's cache is content-addressed and host-wide, so the author's own earlierlnitedit had already primed an identical-content context entry. I separately confirmedCOPY . .is genuinely content-sensitive (appending a comment toscript/lintinvalidated it and the linter ran, 57s).0 issues.both times, exit 0.os.Setenv—4 issues:acrosserrcheck/revive/gochecknoglobals, exit 2, every finding line reaching the terminal in full. The tee copies, it does not swallow.[ "$rc" -eq 0 ], so a findings-bearing run (which does echo source text) can never satisfy the grep.Dockerfile.lintcontains exactly one linter invocation,config verifyis silent on success, and BuildKit prints onlyCACHED— no other line in the build output can match[0-9]+ issues[.:]. Could not construct one.output.formats.json) into.golangci.yml, the one realistic way the summary could vanish under a canonical-config sync. golangci-lint v2.12.2 still prints0 issues.to stderr alongside the JSON, and the guard passed. Reverted, config byte-identical.set -eu: the&& echo 0 || echo $?list suppressesset -e,$?in the||arm is the faileddocker build's, and the compound always succeeds soteealways runs. Worst case (empty rcfile from a kill) still exits non-zero.Untouched as required
Dockerfile.lintand bothRUN --network=nonesteps unchanged;RUN script/fetch-assetsstill in the builder stage with no network restriction (it fetched over the network during the gate); digestsha256:5cceeef0...intact; #120 not attempted.Gate (my clone,
93be4d2)make checkexit 0 in 1m15s, zero(cached)test lines, real per-package durations, lint executed in-container (#11 59.45 0 issues.).docker build --no-cache-filter=lint --no-cache-filter=builder --progress=plain --output=type=cacheonly .exit 0 in 3m3s; zero(cached)test lines; whole graph executed throughstage-2 7/7.--output=type=cacheonlydoes not weaken this — BuildKit still runs every step, it only skips the image export, and I verified the failure path is still reported under it (the negative control failed the build withcacheonlyin effect).93be4d2(check / check, 2m43s).(closes #109); basenext;TODO.mduntouched; authorclawbot; no Claude/Anthropic references or attribution trailers;make fmt-checkclean; inclusive terminology clean.Notes, not defects
sneak@sneak.berlinwhile the author isclawbot@eeqj.de, from the shared host git config. Squash-merge rewrites it anyway.mktemp -tis not POSIX and behaves differently on BSD/macOS (template treated as a prefix), but it creates a usable file on both.--output=type=cacheonlyand--no-cache-filterboth require BuildKit; on a pre-BuildKit docker they error loudly rather than silently pass.script/lintpropagated everywhere. Close it as superseded here and let sneak/prompts#40 carry the cross-repo half.No containers or images left behind; my clone is byte-identical to
93be4d2. No prune of any kind was run.93be4d2846toc568441dc6c568441dc6tocc5836d1f9FAIL — needs-rework
Scoped confirmation pass on the
README.mdmerge atcc5836d, not a full re-review. I relied on #165 (comment) and #165 (comment) for the digest resolving to v2.12.2, the--network=noneenforcement proof, theconfig verifyreversal, thescript/bootstrapconflict resolution, andmake checkwith no host golangci-lint onPATH, and did not redo those.The four non-
README.mdfiles are unchanged from the twice-reviewed head.93be4d2was force-pushed out of ref advertisement so I could not fetch it into my clone; I compared git blob SHAs and file modes served by the Gitea API at93be4d2againstgit rev-parse HEAD:<path>in my clone — identical for all four, content and mode:A blob SHA is the content hash, so this is byte-identity. Prior coverage holds; no escalation to full review.
Blocker
README.mdlines 1447-1449 — a statement this PR falsified, missed by the merge.Before this PR the image ran exactly four
maketargets:fmt-check,lint,test,build. This PR'sDockerfilechange replacesmake lintwith a directgolangci-lint config verify+golangci-lint run, so the image now runs threemaketargets plus a direct binary invocation. There are no longer four check targets.This is the same falsehood the commit already corrects fifteen lines below, in the CI-gate-honesty bullet —
make fmt-check,make lint,make test,make buildwas changed tomake fmt-check,golangci-lint,make test,make build. The prose sentence counts the identical four and was left standing, in a paragraph whose second half this commit rewrote. Given #151's premise that the README contains no false statements, and given the author's own applied standard on the sibling bullet, this one has to go too.Acceptable: drop the target count, e.g. "the checks run inside the image", or enumerate as the bullet below now does.
Secondary, non-blocking — fix in the same pass
README.mdline 1403, Linting section: "Two properties are load-bearing:" then lists--no-cache-filter=lintandRUN --network=none. There is a third, added at the previous review's demand:script/lintdoes not trust--no-cache-filterand fails unless golangci-lint's own summary line appears in the build output. As written the section presents the flag as the guarantee, when the code deliberately treats it as untrusted — which is the more interesting half. Thescript/lintheader comment covers it; the README does not.Gate — my clone,
cc5836d, all viamake/script/make check: exit 0, 63s, zero(cached)test lines, all 12 packages with real durations, lint executed in-container (#11 52.60 0 issues.).docker build --no-cache-filter=lint --no-cache-filter=builder --progress=plain --output=type=cacheonly .: exit 0, 186s, zero(cached)lines, lint stage genuinely re-executed (#19 56.73 0 issues.), bothRUN --network=nonesteps ran, all 12 packages real in[builder 9/11] RUN make test, graph executed throughbuilder 11/11.make linton an unchanged tree: 53s / 53s,0 issues.both, exit 0,git status --porcelainempty between them.lnit), the two-run form: run 1 executed for real (60s,0 issues., exit 0 — my edit invalidatedCOPY . .); run 2 hit the cache, 0s, and the guard fired — every lint layerCACHED,makeexit 2, full diagnostic. That is the false green reproduced and caught. Restored;script/lintblob back to15de890.(cached)test lines are moot — mymake checkran in a fresh clone with an empty Go test cache, zero cached lines, and passed.cc5836d(check / check, 2m53s).nexthas advanced two commits to5888d14since the PR base.git merge-treeis clean and the new changes (Dockerfilebuilder apt line,README.mdEntrypoints/access-log/CI-honesty regions) do not overlap this PR's hunks. No rebase needed.Verified clean
No #151 correction clobbered —
git diff origin/next HEAD -- README.mdtouches only lint-related lines, so every other statement 151 landed survives verbatim by construction.### Shutdownthen### Lintingthen### Dockerreads coherently; the file has no TOC and the one internal anchor,(#docker)at line 375, still resolves. Docker is genuinely required formake lintand thereforemake checklocally, and the Prerequisites text says so without claiming Docker is needed for tests. One commit; title ends(closes #109); basenext;TODO.mduntouched; no Claude/Anthropic references or attribution trailers; #120 not attempted; Makefile shim count (10 of 16) still accurate.No containers or images left behind;
docker ps -aempty. No prune of any kind was run.cc5836d1f9to376ec2de92376ec2de92to1076edb2e8PASS
Round-four scoped confirmation pass on the two README passages only. Behaviour of the change itself is relied on from the three prior reviews (#issuecomment-62467, #issuecomment-62616, #issuecomment-62702): false-green reproduction, digest pin,
config verify's value,--network=none, the negative control, thescript/bootstrapresolution, and the #151 merge.Scope confirmed README-only. Fresh clone at
1076edb:git rev-parse HEAD:<path>givesDockerfile.lint9a9af51,script/bootstrap49001f8,script/lint15de890— the blobs the third review recorded.git diff origin/next HEAD -- Dockerfileis still only the lint-stage change; thejqline isnext's.Passage 1 (CI gate) is true against the
Dockerfile: lint stage runsmake fmt-check,golangci-lint config verify,golangci-lint run; builder runsmake testandmake build, and depends on lint viaCOPY --from=lint. All four claimed properties (formatted, linted, tested, compiled) hold, and the dropped count is no longer asserted.Passage 2: exactly three bullets, all accurate. The unmatched-stage claim verified empirically —
--no-cache-filter=lnit, run 1 executed for real (54.4s,0 issues., exit 0, no warning from docker), run 2 replayedCOPY . .and both lint steps asCACHEDin 0.33s and the summary-line guard fired with exit 2.script/lintrestored, blob back to15de890.No sixth false statement found. Read end to end against the tree: Prerequisites, Quick Start, Development Commands, Entrypoints (14 bullets vs 14 files in
script/;script/ci-mark-supersededbullet present and correctly claims no Makefile target), repo tree, Linting, Docker, CI gate honesty. Checked the countable claims rather than the prose: Makefile has 16 targets, 10 shimmingscript/andbuild/run/dev/deps/clean/cssinline;.dockerignorereally excludes*.md,LICENSE,.editorconfigand really does not exclude.ci-fingerprint;go.modisgo 1.26.1; bothDockerfileandDockerfile.lintcarry the samesha256:5cceeef0...digest, so "the same pinned linter version the gate does" holds; the#ci-gate-honestyanchor resolves and the "see Linting below" / "see Linting above" cross-references point the right way. No stale claim thatmake bootstrapinstalls the linter survives anywhere.Gate re-run here, not taken on report:
make checkexit 0 aftermake bootstrapin the fresh clone; lint stage executed (49.3s,0 issues.).docker build --no-cache-filter=lint --no-cache-filter=builder --progress=plain --output=type=cacheonly .exit 0 in 2m43s; 0(cached)test lines; 13 packages with real durations; graph ran throughbuilder 11/11.make linton an unchanged tree: 53.0s and 48.9s, both exit 0 (a cached replay is 0.33s), tree clean between them — #109 definition-of-done item 2.make fmtwrites nothing;git statusclean. CIsuccesson1076edb. Mergeable, clean against bothnextandmain. One commit, title ends(closes #109), basenext,TODO.mduntouched. No Claude/Anthropic reference or attribution trailer anywhere in the diff or the tree.Notes, none blocking:
script/testandscript/fmt-checkrun on the host" is true ofmake check's components, which is its subject, but reads as a global claim aboutscript/;script/fmt,script/precommit,script/bootstrapand others also run on the host. Wording only.REPO_POLICIES.mdline 105 still specifies a lint stage runningmake fmt-checkandmake lint. ThisDockerfilerunsgolangci-lintdirectly, which is necessary (make lintwould need a docker daemon inside the build) and is disclosed in the file header and the PR body. Correctly left to the canonical rollout..gitea/workflows/check.ymlnames its build step "Build Docker image (runs make check)"; the image has never runmake check.script/lintin my own clone, reverted and verified by blob hash. Nothing committed or pushed. My own secondmake testshowed 13(cached)lines because it followedmake checkin the same clone; the authoritative figure is the image gate's 0.No containers started or left behind, nothing tagged, no prune of any kind.