ADR-0001: golangci-lint as Go quality gate
Status
AcceptedTags
go, linting, ci, code-quality, static-analysisDecision
Use golangci-lint for all Go backend static analysis. Run it in CI as a non-blocking parallel job, with the migration path to blocking deploys once the baseline is clean.Why
The Go backend had no automated quality gate. Issues like unchecked errors, deprecated API usage, dead code, and style inconsistencies accumulated silently. golangci-lint runs multiple linters in a single pass with low overhead and integrates directly into GitHub Actions via the officialgolangci/golangci-lint-action.
A non-blocking CI job was chosen first to establish a clean baseline without halting existing deploys.
Enabled linters
| Linter | Purpose |
|---|---|
govet | Detects common Go mistakes (misaligned structs, suspicious printf calls) |
staticcheck | Advanced static analysis — deprecated APIs (SA1019), dead loops (SA4004), style (QF1012) |
unused | Flags unexported symbols never referenced |
errcheck | Enforces acknowledgment of all error return values |
gocritic | Code pattern checks — deprecatedComment, ifElseChain, elseif, exitAfterDefer |
gofumpt | Stricter gofmt formatter enforced as a linter |
gosimple | Simplification suggestions (subset of staticcheck) |
errcheck is excluded on _test.go files to avoid noise from test assertions.
Rules for agents
- Never use
defer x.Close()without//nolint:errcheck— defer cannot capture the return value - Use
_ = fn()for fire-and-forget calls where the error is intentionally ignored (event publish, cache write) - Use
_, _ = fmt.Sscanf(...)and_, _ = fmt.Fprintf(...)— both return(int, error) - Use
t.Setenv(key, val)in tests, neveros.Setenv+defer os.Unsetenv - Deprecated functions must have a blank line before
Deprecated:in the godoc comment - Collapse
else { if cond {} }toelse if cond {}— gocriticelseifrule - Rewrite long
if / else if / else ifchains asswitch— gocriticifElseChainrule
Bad pattern (do not generate)
Good pattern
CI setup
- Job:
lint-goinbackend-flyio-deploy.yml - Runs in parallel with build/test, non-blocking (no
needs:dependency on deploy yet) - Uses
golangci/golangci-lint-action@v6pinned tov1.64 - Config:
backend/go/.golangci.yml
Migration path to blocking
Addneeds: [lint-go] to the deploy job once the lint baseline is clean and the team has a session or two of experience with the rules.