golangci-lint Configuration
golangci-lint runs many Go analyzers behind one config file and one CLI.
Teams use .golangci.yml to choose linters, set severity, exclude generated trees, and keep local runs aligned with CI.
Recipe
Quick-reference recipe card - copy-paste ready.
# Install pinned version (match CI)
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6
# Initialize config interactively
golangci-lint config verify
# Run with repo config
golangci-lint run ./...
# Auto-fix what is safe
golangci-lint run --fix ./...When to reach for this:
go vetis clean but review still catches the same staticcheck issues.- Onboarding multiple services to one platform linter policy.
- Tuning noise from protobuf, mocks, or
vendor/. - Splitting lint jobs per module in a
go.workmonorepo.
Working Example
Starter .golangci.yml for a single service module:
run:
timeout: 5m
modules-download-mode: readonly
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gosec
- revive
- bodyclose
disable-all: true
linters-settings:
govet:
enable-all: true
revive:
rules:
- name: exported
disabled: true
gosec:
excludes:
- G104 # noisy for fmt.Fprintf to http.ResponseWriter
issues:
exclude-dirs:
- vendor
- third_party
exclude-files:
- ".*\\.pb\\.go$"
- ".*_mock\\.go$"
max-issues-per-linter: 0
max-same-issues: 0Makefile:
GOLANGCI_LINT_VERSION ?= v2.1.6
.PHONY: lint
lint:
golangci-lint run ./...CI job:
- uses: actions/setup-go@v5
with:
go-version: '1.26.x'
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v2.1.6
args: ./...What this demonstrates:
disable-all: truethen explicitenablekeeps the set intentional.exclude-filesskips generated protobuf and mocks.- Pinning version in Makefile, action, and docs prevents "works locally" drift.
Deep Dive
How It Works
- golangci-lint loads packages once and runs enabled linters in parallel where safe.
- Each linter wraps upstream tools (staticcheck, gosec, revive, etc.).
- Findings merge into one report with linter name prefix (
staticcheck,govet, ...). - Config can live at repo root; child modules may carry overrides.
Recommended Enable Progression
| Stage | Linters | Goal |
|---|---|---|
| 1 | govet, errcheck, gosimple, ineffassign, unused | Baseline correctness |
| 2 | staticcheck | Deeper API and dead code |
| 3 | revive, stylecheck | Consistent naming and comments |
| 4 | gosec, bodyclose | Security and HTTP resource leaks |
| 5 | custom plugins | Org-specific APIs |
Monorepo Patterns
# Root: shared defaults
run:
relative-path-mode: gomod
issues:
exclude-dirs:
- vendorPer-module services/billing/.golangci.yml:
linters:
enable:
- gosec
linters-settings:
gosec:
severity: mediumRun from workspace root:
golangci-lint run ./services/billing/...
golangci-lint run ./services/... # all modules with go.modSuppressions
//nolint:gosec // G304: path validated by caller contract; see SEC-123
os.Open(userPath)Prefer narrow //nolint with ticket over broad exclude-rules regular expressions.
Go Notes
# Verify config syntax
golangci-lint config verify
# List enabled linters
golangci-lint linters
# Debug single linter
golangci-lint run --disable-all -E staticcheck ./...Gotchas
- Unpinned golangci-lint in CI - New releases enable linters by default. Fix: Pin version in action and document bump process.
- Linting entire monorepo without timeouts - Large repos OOM or hit 1m default. Fix: Set
run.timeout: 5mand shard per module in CI matrix. - Excluding too much with regex - Hides real bugs in
internal/legacy. Fix: Time-boxed exclude with refactor ticket. - Running
--fixblindly in CI - Auto-fix can change semantics for some linters. Fix: Run--fixlocally or in bot PRs, not silent main commits. - Mismatched Go version - Linters use type info from module's
godirective. Fix: CIgo-versionmatchesgo.mod. - Duplicate vet via
go vetand golangci-lint - Redundant but acceptable; drop standalonego vetifgovetenable-all is on.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
go vet + staticcheck CLI | Minimal dependencies | You want one report and shared excludes |
Custom go/analysis multichecker | Full control, few linters | You need 20 community linters day one |
| Review-only policy | Never at scale | - |
Per-package go:generate lint | Legacy incremental | Replacing centralized config |
FAQs
Where should .golangci.yml live?
Repository root for single-module repos.
Monorepos often use root defaults plus per-module files next to go.mod.
How do I migrate from golangci-lint v1 to v2?
Run golangci-lint migrate and read the v2 changelog.
Update CI action version and config schema together.
Should tests be linted?
Yes.
Many bugs appear only in _test.go files (testinggoroutine, errcheck on failures).
How do I lint only changed packages in CI?
Use --new-from-rev=origin/main or path filters in the workflow for speed.
Keep a nightly full-repo job for drift.
What about go.work workspaces?
Run golangci-lint per module directory or use path patterns ./... from workspace root with relative-path-mode: gomod.
Can I enforce max line length?
revive and lll (often disabled as noisy) cover this.
Go community prefers readability over hard column limits; use review for egregious lines.
How do presets compare to manual enable lists?
Presets like standard bundle opinionated sets.
Explicit enable lists are clearer for audit and onboarding docs.
Does golangci-lint replace gofmt?
It can run gofmt and goimports linters.
Most teams still enforce format separately for speed and clarity.
How do I baseline legacy issues?
Use issues.new exclusions temporarily or fix in dedicated sprint.
Avoid permanent exclude-rules without owners.
Are custom linters worth it?
After staticcheck and gosec exhaust generic rules.
Build on go/analysis when APIs are internal and stable.
Related
- go vet & Common Analyzer Diagnostics - vet messages explained
- staticcheck & golangci-lint in CI - CI integration patterns
- CI Quality Gates: Lint, Test, Race, Vuln - lint job placement
- Pre-commit Hooks & Review Checklists - local lint scope
- Linting & Code Quality Best Practices - adoption rules
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).