Go Linting
Treat lint configuration as executable project policy, not a universal checklist. Start with correctness and maintainable signal, then add opinionated checks only when the team accepts their trade-offs.
Inspect first
- Read repository instructions,
go.mod, CI workflows, Makefile/task runner, existing lint configuration, and pinned tool versions. - Prefer the repository's command over invoking a global binary directly.
- Determine whether the task is diagnosis, a focused fix, configuration, or migration, and keep output at that scale.
- Use the project-pinned linter version. A different binary can reject or reinterpret existing configuration.
Running checks
Use the narrowest established command that answers the task. Typical commands, only when the relevant tools already exist, are:
gofmt -d ./path/to/files
go vet ./path/to/affected/...
golangci-lint run ./path/to/affected/...
Some tools do not accept the same package or file syntax; follow their help and the project wrapper. Run --fix, formatter rewrites, or generated-code changes only when edits are requested, and review the resulting diff.
Classify diagnostics before changing code:
- probable correctness, security, or resource-lifecycle issue;
- API or compatibility constraint;
- project policy violation;
- opinionated readability suggestion;
- false positive or unsupported generated/vendor code.
Fix the underlying issue when it is within scope. Do not contort behavior merely to satisfy a heuristic.
Configuration
When creating or updating golangci-lint configuration:
- Confirm the binary's major version and any repository pin.
- Consult the current official schema or the installed tool's help.
- Start with a small baseline whose diagnostics the team will act on.
- Add one coherent category at a time and run it against the actual repository.
- Configure generated, vendor, fixture, and example paths intentionally.
- Verify the config with the installed binary and run it in the same way CI will.
- Record why noisy or conflicting checks are enabled, disabled, or scoped.
Do not copy a large “recommended” config blindly. Linter names, defaults, configuration keys, and supported Go versions change. Read linter selection for stable selection criteria.
Suppressions
A suppression is part of the code's maintenance contract. Prefer a specific linter name and a concise reason that explains why the flagged pattern is safe or required:
result := value.(Result) //nolint:forcetypeassert // protocol validation guarantees Result here
Keep the directive as narrow as the tool permits. Re-check it when surrounding invariants change. See nolint directives.
Incremental adoption
For a legacy codebase, avoid a single mechanical cleanup that mixes behavior, formatting, and policy. Establish a baseline or changed-code gate using the project's supported configuration, fix high-confidence correctness findings first, and ratchet scope deliberately. Ensure local and CI commands use the same binary and config.
Report the commands run, tool version, affected scope, remaining diagnostics, suppressions added, and any check intentionally deferred.