Go linting
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode.
npx -y skills add muratmirgun/gophers --skill go-lintingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when setting up linting for a Go project, configuring golangci-lint, picking a linter set, suppressing findings with //nolint, or wiring lint checks into CI. Apply proactively whenever a project lacks .golangci.yml, when lint output is unclear, or when a new package needs the project's quality bar. Does not cover code review process (see go-code-review).
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
7.6 KB, as published. Nobody here has run it
Go Linting
The single most important property of a linting setup is consistency: every contributor and every CI run uses the same rules. golangci-lint is the tool; a checked-in .golangci.yml is the contract.
Core Rules
- Every Go project has a
.golangci.ymlat the repository root. It is the source of truth for which linters run. - Lint runs in CI on every PR. A green build means lint is green.
- Lint runs locally before commit. A pre-commit hook or
make lintkeeps the feedback loop fast. - Suppress with reasons.
//nolint:linter // why— never bare//nolint. - Fix the cause first. A suppression should be the last resort, not the default reaction.
- Never silence security linters (
gosec,bodyclose,sqlclosecheck) without a strong, documented reason.
Setup Procedure
- Install:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest(orbrew install golangci-lint). - Drop a baseline
.golangci.ymlat the repo root. - Run
golangci-lint run ./.... - Fix the findings in order — formatting first,
govetnext, style last. - Re-run until clean. Commit
.golangci.ymland any source fixes together. - Add the CI workflow (see references/ci-integration.md).
Minimum Linter Set
These five catch the most common issues and have the lowest noise rate. Start here:
| Linter | Catches |
|---|---|
errcheck | Unchecked error returns |
govet | Mistakes that compile but are wrong (printf args, shifts, etc.) |
staticcheck | Bug-prone patterns, dead code, simplifications |
ineffassign | Assignments whose value is never read |
revive | Style issues (modern replacement for golint) |
Add formatting on top: gofmt / goimports (or gofumpt for stricter rules). With golangci-lint v2 these run via golangci-lint fmt.
Recommended Additional Linters
Enable these once the baseline is clean:
| Linter | When to enable |
|---|---|
gosec | Any service that handles untrusted input |
bodyclose | Any code that calls http.Client.Do |
sqlclosecheck | Any code using database/sql |
nilerr | Any code that does if err != nil { return nil } style returns |
misspell | Always — comments and strings |
unconvert | Always — flags useless type conversions |
nolintlint | Always — enforces the //nolint rules below |
paralleltest | If most tests can use t.Parallel() |
thelper | If you write test helpers (enforces t.Helper()) |
testifylint | If the project uses testify |
gocyclo / gocognit | When you want a complexity ceiling |
exhaustive | When you use iota-based enums and want full switch coverage |
Read references/linter-catalog.md when picking from the long tail of correctness, style, security, and complexity linters, or when deciding which ones to enable on legacy code.
Development Workflow
lint:
golangci-lint run ./...
lint-fix:
golangci-lint run --fix ./...
fmt:
golangci-lint fmt ./...
ci-lint:
golangci-lint run --new-from-rev=origin/main ./...
| Task | Command |
|---|---|
| Run all enabled linters | golangci-lint run ./... |
| Auto-fix everything fixable | golangci-lint run --fix ./... |
| Format the tree (v2+) | golangci-lint fmt ./... |
| Lint only changed code | golangci-lint run --new-from-rev=origin/main ./... |
| Run one linter | golangci-lint run --enable-only=govet ./... |
| Show which linters exist | golangci-lint linters |
--new-from-rev is what makes incremental adoption work: legacy code stays untouched, new and changed code must meet the bar.
Read references/ci-integration.md when wiring GitHub Actions, pre-commit hooks, or selective linting on PRs.
Suppressing Findings
// Good: specific linter + a reason
//nolint:errcheck // fire-and-forget; Sync error is not actionable on shutdown
_ = logger.Sync()
// Bad: blanket, no reason — nolintlint will flag this
//nolint
_ = logger.Sync()
Rules (enforced by nolintlint):
- Name the linter:
//nolint:errcheck, not//nolint. - Include a justification after
//. - Place the directive on the same line as the finding, or immediately above the construct it applies to.
- Prefer per-line suppressions over file-level
//nolint:all.
Read references/nolint-directives.md when deciding between inline, block, and file-scope suppressions, or when reviewing existing
//nolintfor stale rationale.
Interpreting Output
Each finding looks like:
path/to/file.go:42:10: message describing the issue (linter-name)
The linter name in parentheses is the key — look it up in the catalog to see what it actually checks, then either fix the code or suppress with a reason that names the same linter.
Anti-Patterns
| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
No .golangci.yml in the repo | Each contributor lints differently or not at all | Commit a baseline config; CI enforces it |
//nolint with no linter name | Disables every check on that line, silently | //nolint:errcheck // reason |
//nolint:all at the top of a file | Whole file escapes review | Suppress per construct with a reason |
| Lint failures non-blocking in CI | "Green build" becomes meaningless | Block merges on lint failure |
| Enabling 100 linters on day one | Noise drowns signal; team gives up | Start with the minimum set, add gradually |
Suppressing gosec / bodyclose without justification | Silently hides real bugs | Fix the cause; if you can't, document why in the suppression |
| Different lint versions in dev vs CI | "Works on my machine" comes back | Pin the version in CI and document it in the README |
| Linting after the fact, only in CI | Slow iteration; PRs ping-pong | Run locally via make lint or a pre-commit hook |
Verification Checklist
Before merging a linting change:
-
.golangci.ymlexists at the repo root and is checked in -
golangci-lint run ./...is clean (or--new-from-revreports no new issues) - CI runs
golangci-lintand blocks merges on failure -
nolintlintis enabled; no bare//nolintremains - Every
//nolintincludes a linter name and a one-line reason - Security linters (
gosec,bodyclose,sqlclosecheck) are enabled where applicable
References
- assets/.golangci.yml — production-ready baseline configuration
- references/linter-catalog.md — what each linter checks and when to enable it
- references/nolint-directives.md — suppression patterns, scoping rules, anti-patterns
- references/ci-integration.md — GitHub Actions, pre-commit hooks, incremental adoption