Adopt the org-standard .golangci.yml and golangci-lint v2.12.2 #14
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Problem
This repo is not on the org-standard Go lint configuration, and the linter version pinned in the backend Docker build is two minor versions behind what is now deployed across the org's Go repos.
Verified on
mainatfbfe1df:.golangci.ymlcontent mismatch.REPO_POLICIES.mdstates: ".golangci.ymlis standardized and must NEVER be modified by an agent, only manually by the user. Fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml."Linter version mismatch.
Dockerfile.backendpins golangci-lint at commit9f61b0f53f80672872fced07b6874397c3ed197b(v2.7.2, dated 2026-02-27). The org standard is v2.12.2, commitc0d3ddc9cf3faa61a4e378e879ece580256d76e5.make checkinbackend/currently reports0 issues.against the non-standard config. Moving to the stricter standard config plus a newer linter is expected to surface new findings; fixing those findings is part of this work unit.Definition of done
backend/.golangci.ymlis byte-identical to the org standard —sha256sum backend/.golangci.ymloutputs021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb. Copy it verbatim; do not hand-edit, tune, or add exclusions to it.Dockerfile.backendpins golangci-lint to commitc0d3ddc9cf3faa61a4e378e879ece580256d76e5, with a comment above the reference reading the version and date in the form# golangci-lint v2.12.2 (YYYY-MM-DD)per the hash-pinning policy..golangci.ymlis explicitly forbidden. A targeted//nolint:linter // reasonon an individual line is acceptable only where the finding is genuinely a false positive, and each one must carry a written justification comment.cd backend && make checkpasses and reports0 issues.make checkat the repo root still passes.docker build -f Dockerfile.backend .succeeds.TODO.mdis updated in the same commit as the work.(closes #N)for this issue number.Implementation requirements
maketargets andscript/entrypoints only. Never invokego,gofmt, orgolangci-lintdirectly..golangci.ymlin any way after copying it. If the standard config disagrees with the code, the code changes.Escalation: the current config is not merely non-standard, it is invalid and silently inert
Follow-up audit found the problem is worse than a hash mismatch.
backend/.golangci.ymldeclaresversion: "2"on line 1 but uses golangci-lint v1 schema below it:linters-settings:key. In v2 this moved tolinters.settings:.issues.exclude-use-default: false, which does not exist in v2 and is absent from the canonical file.Verified empirically with golangci-lint v2.10.1:
Why this matters more than the version bump
Every threshold in the file is inert:
lll: 88,funlen: 80/50,cyclop: 15,dupl: 100are all ignored.lllfalls back to its default of 120 columns.So when
cd backend && make checkprints0 issues., that is not evidence the code is clean — it is evidence the linter ran with defaults and found nothing at defaults. The green result we have been relying on is meaningless.Concretely, three lines already exceed the 77-character hard wrap required by
CODE_STYLEGUIDE_GO.md, and go unreported today:internal/server/server.go:65— 93 charsinternal/server/server.go:97— 81 charsinternal/reportbuf/reportbuf.go:166— 88 charsAll three are long
//nolintjustification comments;lllwould catch them once the config is valid.Consequences for this issue
sha256sum backend/.golangci.ymlto equal021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb. Add one verification step: after copying, run the linter's own config verification and confirm it reports the config valid. A config that fails to verify must never be committed again.lllviolations above are in scope.This also raises the priority of this issue above the other compliance items: until the config is valid, no other issue's "lint passes" claim can be trusted.
Implementation plan
Branch
feat/golangci-standard-configoffmainatfbfe1df.Copy the org standard
.golangci.ymlverbatim overbackend/.golangci.yml(target sha256
021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb).No edits, no added exclusions.
Repoint the golangci-lint pin in
Dockerfile.backendfrom9f61b0f53f80672872fced07b6874397c3ed197btoc0d3ddc9cf3faa61a4e378e879ece580256d76e5, and update the comment above itto
# golangci-lint v2.12.2 (2026-08-09).Make the config-validity check permanent rather than a one-off manual step.
The escalation asks for
golangci-lint config verifyto be run aftercopying; a one-time run does not stop the schema from rotting again. So
backend/Makefile'slinttarget gains agolangci-lint config verifyline ahead of
golangci-lint run ./.... From then onmake lint— andtherefore
make check, the pre-commit hook, and the Docker build — fails onan invalid config instead of silently falling back to defaults. This also
keeps the verification reachable through a
maketarget, so nothing has toinvoke the linter by hand.
Fix every surfaced finding in the Go source. No
.golangci.ymledits.Update
TODO.mdin the same commit, correcting the stale Status/Next Step(it still describes
feat/reportbuf-storageas unmerged; it is merged).Gate on
cd backend && make check, rootmake check, anddocker build -f Dockerfile.backend .(viamake docker, which is whatexercises the pinned v2.12.2 rather than whatever is on the local PATH).
Scope note on the three lines called out in the escalation
The canonical config sets
lll.line-length: 88, so of the three lines listedonly one is an actual lint finding:
internal/server/server.go:65— 93 chars, is anlllfinding.internal/server/server.go:97— 81 chars, under 88, not reported.internal/reportbuf/reportbuf.go:166— 88 chars, exactly at the limit, notreported.
All three do violate the 77-column hard wrap in
CODE_STYLEGUIDE_GO.md, andthe escalation puts all three in scope, so all three get wrapped. In each case
the length comes from a long
//nolintjustification on the code line; the fixis to move the justification to a preceding comment block and leave a short
//nolint:linter // see comment aboveon the line itself. The suppressionsthemselves are unchanged in meaning and are not being widened.
Expected outcome: one commit, no split needed. Will report back if the v2.12.2
run inside the Docker build surfaces materially more than the local v2.10.1 run
did.
No split needed — delivered as one PR
PR #31 on branch
feat/golangci-standard-config.The escalation warned to budget for "substantially more" findings once the
canonical config actually loaded, and to split into "adopt config + linter
version" and "fix the findings" if it did not fit one commit. It fit: with the
standard config loading correctly under v2.12.2 the backend produced exactly
one finding,
llloninternal/server/server.go:65.The linters that had been silently inert under the broken config —
funlen,cyclop,dupl, andlllat its real 88-column threshold instead of the120-column default — had nothing further to report. The backend is small
enough that the defaults it had been accidentally linted against were not far
off the intended thresholds.
Two corrections to the escalation's line inventory, for the record:
internal/server/server.go:97(81 cols) andinternal/reportbuf/reportbuf.go:166(88 cols) are notlllfindings.The canonical config sets
lll.line-length: 88, andlllreports linesstrictly over the limit, so 81 passes and 88 sits exactly at it.
CODE_STYLEGUIDE_GO.md, and theescalation explicitly scoped them in, so both were wrapped alongside the
genuine finding.
All three were long
//nolintjustifications on the code line; eachjustification moved to a comment block above, leaving a short directive behind.
No suppression added or widened, and
.golangci.ymluntouched after theverbatim copy.
On the verification requirement: rather than running
golangci-lint config verifyonce by hand, it is now the first step of the backendlinttarget, soit runs on every
make check, every pre-commit, and every Docker build. Aninvalid config fails the build from now on instead of silently degrading to
defaults — which is the failure this issue exists to close out.
Correction to my earlier escalation comment
My escalation listed three lines as
lllviolations that would surface once the canonical config loaded. Two of those three were wrong, and the implementer was right to push back rather than accept the list as given.Verified directly against the canonical config at
/home/user/dev/prompts/.golangci.yml:lllreports lines strictly over 88. So:lllfinding?internal/server/server.go:65internal/server/server.go:97internal/reportbuf/reportbuf.go:166Where my escalation went wrong: I carried over the audit's framing, which measured those lines against the 77-column hard-wrap guidance in
CODE_STYLEGUIDE_GO.md("Try to hard wrap long lines at 77 characters or less") and then described them as thingslllwould catch. Those are two different standards. 77 is styleguide guidance; 88 is what the linter actually enforces. Conflating them produced a false prediction, and the "expect substantially more findings" warning that followed was built on the same overreach.The implementer wrapped all three anyway, which is fine — the escalation explicitly scoped them in, and they do violate the 77-column guidance even though only one violates
lll. But the single reallllfinding is the honest count, and the anticipated second PR was correctly not created.Recording this because the wrong claim is durable once it is in the tracker, and because the general lesson applies to the rest of this backlog: several other issues I filed cite audit findings I did not personally re-derive. Implementers should verify the specific line numbers and thresholds in an issue against the source before treating them as fact — and say so when they do not hold up, as happened here.
None of this changes the definition of done or the core finding: the previous config was genuinely schema-invalid and genuinely inert, and that part stands.