# golangci-lint configuration for the backend module. # # Every enable/disable is justified by a MEASURED count from the report-only sweep of 2026-08-03 # (golangci-lint 2.12.2, 205 .go files, 115 of them tests). Raw sweep = 435 findings: # errcheck 413, staticcheck 17, misspell 4, unused 1; govet / ineffassign / nolintlint 0. # Those counts are the PROVENANCE of each decision below, not the state of the tree: everything they # name has since been fixed or excluded, so a clean run today reports 0. # # Owner's constraint (D39.92) outranks coverage: standards must not slow development or push a # model into hacks. A rule that produces noise is switched OFF here with its reason on one line — # never worked around in the code — and nolintlint refuses a bare //nolint. version: "2" linters: default: none enable: # --- the standard set --- - errcheck # 413 raw; the exclusions below account for 401 of them, the rest were fixed - govet # 0 — already in the manual battery; enabling pins it - ineffassign # 0 - staticcheck # 17 raw; 6 excluded as QF1001, 11 fixed - unused # 1 — a real dead method (pipeline.applyBanknote), deleted by this pack # --- zero-cost bug classes: 0 findings today, so they cost nothing and cannot regress --- - bodyclose # a leaked provider response body is a transport leak - copyloopvar - durationcheck - makezero # --- small real tails --- - errorlint # 3 — wrapped-error comparison is a live correctness class here - misspell # 4, all one word (see exclusions) - nilerr # 3 - rowserrcheck # 2 - sqlclosecheck # 1 - nolintlint # 0 today — enforces the anti-hack rule: no silent suppressions # DISABLED ON PURPOSE (measured, not skipped): # exhaustruct — 988 findings module-wide. Scoped to the two types it was proposed for # (config.Stage, pipeline.Request) it is 17, but 16 are test literals and the 17th is the SEAM # ITSELF (config/internal_call.go Stage()) — the one construction site that is allowed. The # invariant "Stage is built only through the seam" needs a rule that tells seam from bypass; # exhaustruct cannot, the analyzer can. Off so it does not compete with that guard. settings: staticcheck: checks: - all - -ST1000 # default-off in golangci-lint: package comment form - -ST1003 # default-off: naming conventions - -ST1016 # default-off: receiver name consistency - -ST1020 # default-off: comment form on exported methods - -ST1021 # default-off: comment form on exported types - -ST1022 # default-off: comment form on exported vars - -QF1001 # 6 findings: "apply De Morgan's law". The current shape mirrors the prose above # each condition ("neither X nor Y"); rewriting for the linter reads worse # than the rule it encodes. A style opinion, not a defect class. misspell: ignore-rules: # `initals` is a DELIBERATE typo: the langpack tests write it into a fixture to prove the # parser reports unknown keys instead of silently dropping them. Correcting it deletes the # test. 1 comment + 3 test sites. NOTE: `locale` is left unset on purpose — setting it to US # makes misspell rewrite en-GB spellings in the English prose (behaviour, honoured, dialogue): # measured 0 findings unset against 311 with `locale: US`, none of them real errors. - initals nolintlint: require-explanation: true # a suppression without a reason is the hack this rule exists to stop require-specific: true # //nolint must name the linter, never blanket-silence a file allow-unused: false exclusions: # Written out instead of the std-error-handling preset: that preset also swallows os.Setenv and # os.Remove, and one of the two real production findings in this repo is an unchecked os.Setenv. rules: # Close/Flush: 294 sites. All 33 production ones are readers or DB handles (listed and # checked one by one). The repo has exactly ONE flush-on-Close writer — chunktest/epub.go's # zip.Writer — and its Close IS checked, so this cannot hide a lost write. - linters: [errcheck] text: 'Error return value of `[^`]*\.(Close|Flush)` is not checked' # CLI rendering to stdout: 91 sites. A broken pipe on `tmctl status | head` is not something # the CLI can act on, and threading it out would rewrite every renderer for nothing. - linters: [errcheck] text: 'Error return value of `fmt\.Fprint(f|ln)?` is not checked' # hash.Hash.Write is documented never to return an error (2 sites, the membank digest). Keyed on # the METHOD, not on a receiver spelled `h`: a rule that fires for `hasher.Write` but not `h.Write` # pushes the next contributor toward renaming a variable to appease the linter. - linters: [errcheck] text: 'Error return value of `[^`]*\.Write` is not checked' path: internal/membank/ # `defer tx.Rollback()` — 5 sites. The idiom is "roll back unless committed", so on the happy # path the error is sql.ErrTxDone BY DESIGN. Wrapping each in a discard closure would add five # blocks of noise to say nothing. Excluded in config rather than worked around in the code. - linters: [errcheck] text: 'Error return value of `[^`]*\.Rollback` is not checked' # In tests, a response-writer/fixture write that fails cannot be acted on, and a t.Fatal on # each would bury the assertion the test is actually about. - linters: [errcheck] path: _test\.go text: 'Error return value of `(w\.Write|os\.WriteFile|fmt\.Sscanf)` is not checked' # runToSignatureStop returns *WaveSignatureStop, which implements error because the stop travels # the pipeline AS an error — so errcheck sees 12 discarded "errors". The helper has already # t.Fatal'd on every wrong outcome; the return is there for the tests that want to inspect it. - linters: [errcheck] path: internal/pipeline/miningstop_join_test\.go text: 'Error return value is not checked' formatters: enable: - gofmt # `make fmt` checks this too; the duplicate is deliberate, so a bare # `golangci-lint run` is still a complete gate on its own issues: max-issues-per-linter: 0 # never truncate: a hidden tail reads as "clean" max-same-issues: 0