
Golang Lint
- 33.9k installs
- 2.8k repo stars
- Updated July 27, 2026
- samber/cc-skills-golang
golang-lint is a Go skill for golangci-lint configuration and linting best practices.
About
Go linting best practices and golangci-lint configuration for code quality. Covers linter presets, custom rule configuration, CI integration, inline suppression, and output interpretation - essential for maintaining consistent code standards.
- golangci-lint configuration and presets
- Custom linting rules and CI integration
- Inline suppression and output interpretation
Golang Lint by the numbers
- 33,886 all-time installs (skills.sh)
- +504 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #12 of 1,382 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/samber/cc-skills-golang --skill golang-lintAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33.9k |
|---|---|
| repo stars | ★ 2.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | samber/cc-skills-golang ↗ |
How do you lint Go code before committing?
Development teams need consistent linting configuration and CI integration to enforce code quality standards.
Who is it for?
team projects,CI/CD pipelines,code quality enforcement,automated reviews
Skip if: learning linting basics,personal scripts,non-enforced environments
When should I use this skill?
The user asks to lint Go code, configure golangci-lint, fix errcheck or staticcheck findings, or review error handling before commit.
What you get
golangci-lint findings for unchecked errors, deprecated APIs, unused symbols, printf formats, and nil error return antipatterns.
- Linter configuration
- Issue report
- Fix recommendations
By the numbers
- Covers 98 lines of linting configuration guidance
- Includes presets, custom rules, CI integration, and output interpretation
Files
Persona: You are a Go code quality engineer. You treat linting as a first-class part of the development workflow — not a post-hoc cleanup step.
Modes:
- Setup mode — configuring
.golangci.yml, choosing linters, enabling CI: follow the configuration and workflow sections sequentially. - Coding mode — writing new Go code: launch a background agent running
golangci-lint run --fixon the modified files only while the main agent continues implementing the feature; surface results when it completes. - Interpret/fix mode — reading lint output, suppressing warnings, fixing issues on existing code: start from "Interpreting Output" and "Suppressing Lint Warnings"; use parallel sub-agents for large-scale legacy cleanup.
Dependencies:
- golangci-lint:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Go Linting
Overview
golangci-lint is the standard Go linting tool. It aggregates 100+ linters into a single binary, runs them in parallel, and provides a unified configuration format. Run it frequently during development and always in CI.
Every Go project MUST have a .golangci.yml — it is the source of truth for which linters are enabled and how they are configured. See the recommended configuration for a production-ready setup with 48 linters enabled.
Quick Reference
# Run all configured linters
golangci-lint run ./...
# Auto-fix issues where possible
golangci-lint run --fix ./...
# Format code (golangci-lint v2+)
golangci-lint fmt ./...
# Run a single linter only
golangci-lint run --enable-only govet ./...
# List all available linters
golangci-lint linters
# Verbose output with timing info
golangci-lint run --verbose ./...Configuration
The recommended .golangci.yml provides a production-ready setup with 33 linters. For configuration details, linter categories, and per-linter descriptions, see the [linter reference](./references/linter-reference.md) — which linters check for what (correctness, style, complexity, performance, security), descriptions of all 33+ linters, and when each one is useful.
Suppressing Lint Warnings
Use //nolint directives sparingly — fix the root cause first.
// Good: specific linter + justification
//nolint:errcheck // fire-and-forget logging, error is not actionable
_ = logger.Sync()
// Bad: blanket suppression without reason
//nolint
_ = logger.Sync()Rules:
1. //nolint directives MUST specify the linter name: //nolint:errcheck not //nolint 2. //nolint directives MUST include a justification comment: //nolint:errcheck // reason 3. The `nolintlint` linter enforces both rules above — it flags bare //nolint and missing reasons 4. NEVER suppress security linters (gosec, bodyclose, sqlclosecheck) without a very strong reason
For comprehensive patterns and examples, see [nolint directives](./references/nolint-directives.md) — when to suppress, how to write justifications, patterns for per-line vs per-function suppression, and anti-patterns.
Development Workflow
1. Linters SHOULD be run after every significant change: golangci-lint run ./... 2. Auto-fix what you can: golangci-lint run --fix ./... 3. Format before committing: golangci-lint fmt ./... 4. Incremental adoption on legacy code: set issues.new-from-rev in .golangci.yml to only lint new/changed code, then gradually clean up old code
Makefile targets (recommended):
lint:
golangci-lint run ./...
lint-fix:
golangci-lint run --fix ./...
fmt:
golangci-lint fmt ./...For CI pipeline setup (GitHub Actions with golangci-lint-action), see the samber/cc-skills-golang@golang-continuous-integration skill.
Interpreting Output
Each issue follows this format:
path/to/file.go:42:10: message describing the issue (linter-name)The linter name in parentheses tells you which linter flagged it. Use this to:
- Look up the linter in the reference to understand what it checks
- Suppress with
//nolint:linter-name // reasonif it's a false positive - Use
golangci-lint run --verbosefor additional context and timing
Common Issues
| Problem | Solution |
|---|---|
| "deadline exceeded" | Set or increase run.timeout in .golangci.yml; golangci-lint v2 defaults to no timeout (0) |
| Too many issues on legacy code | Set issues.new-from-rev: HEAD~1 to lint only new code |
| Linter not found | Check golangci-lint linters — linter may need a newer version |
| Conflicts between linters | Disable the less useful one with a comment explaining why |
| v1 config errors after upgrade | Run golangci-lint migrate to convert config format |
| Slow on large repos | Reduce run.concurrency or exclude paths with linters.exclusions.paths / formatters.exclusions.paths |
Parallelizing Legacy Codebase Cleanup
When adopting linting on a legacy codebase, use up to 5 parallel sub-agents (via the Agent tool) to fix independent linter categories simultaneously:
- Sub-agent 1: Run
golangci-lint run --fix ./...for auto-fixable issues - Sub-agent 2: Fix security linter findings (bodyclose, sqlclosecheck, gosec)
- Sub-agent 3: Fix error handling issues (errcheck, nilerr, wrapcheck)
- Sub-agent 4: Fix style and formatting (gofumpt, goimports, revive)
- Sub-agent 5: Fix code quality (gocritic, unused, ineffassign)
Cross-References
- → See
samber/cc-skills-golang@golang-continuous-integrationskill for CI pipeline with golangci-lint-action - → See
samber/cc-skills-golang@golang-code-styleskill for style rules that linters enforce - → See
samber/cc-skills-golang@golang-securityskill for SAST tools beyond linting (gosec, govulncheck) - → See
samber/cc-skills-golang@golang-continuous-integrationskill for automated AI-driven code review in CI using these guidelines
version: "2"
run:
concurrency: 4
# Timeout for analysis
timeout: 5m
# Include test files
tests: true
issues:
max-issues-per-linter: 0 # 0 = unlimited (we want ALL issues)
max-same-issues: 50
linters:
enable:
# correctness
- govet # built-in checker: copylocks, printf formats, struct tags, unreachable code
- staticcheck # extensive static analysis: deprecated APIs, common mistakes, simplifications
- unused # unused variables, functions, types
- errcheck # unchecked error returns and type assertions
- errorlint # correct use of errors.Is/As and %w wrapping (Go 1.13+)
- nilerr # returning nil error when err is non-nil
- forcetypeassert # type assertions without comma-ok check
- copyloopvar # loop variable copy issues (Go 1.22+)
- durationcheck # detect time.Duration * time.Duration bugs
- reassign # package-level variable reassignment
# style
- gocritic # opinionated style: unnecessary conversions, range copies, redundant code
- revive # naming conventions, exported types, stuttered package names
- wsl_v5 # whitespace and blank line rules for readability
- whitespace # trailing whitespace, unnecessary blank lines
- godot # exported-symbol comments must end with a period
- misspell # common English misspellings in identifiers and comments
- dupword # duplicate words in comments and strings (the the, is is)
- predeclared # shadowing Go built-ins (len, cap, error)
- errname # error type/var naming conventions (ErrFoo, FooError)
- asciicheck # non-ASCII identifiers (prevents homoglyph/trojan source attacks)
# complexity
- gocyclo # cyclomatic complexity threshold
- nestif # deeply nested if/else chains
- funlen # function length limits (lines and statements)
- dupl # code duplication detection
# performance
- perfsprint # faster alternatives to fmt.Sprintf
- unconvert # unnecessary type conversions
- ineffassign # assignments to variables never read
- goconst # repeated literals that should be constants
# security & resources
- gosec # security scanner: SQL injection, hardcoded credentials, weak crypto, path traversal
- bidichk # dangerous bidirectional Unicode sequences (trojan source CVE-2021-42574)
- bodyclose # unclosed HTTP response bodies (connection leaks)
- noctx # HTTP requests missing context.Context
- containedctx # context.Context stored in struct fields instead of passed as parameter
- fatcontext # context.WithValue/WithCancel in loops (unbounded context chain, memory leak)
- sqlclosecheck # unclosed sql.Rows and sql.Stmt
- rowserrcheck # unchecked sql.Rows.Err() after iteration
# logging
- sloglint # consistent log/slog code style
- loggercheck # key-value pair validation for structured loggers (zap, slog, logr)
# testing
- testifylint # testify best practices
- thelper # test helpers missing t.Helper()
- usetesting # use t.Setenv/t.TempDir instead of os equivalents in tests
- paralleltest # tests and subtests missing t.Parallel()
# modernization & meta
- modernize # old patterns replaceable with newer Go features
- exptostd # replace golang.org/x/exp/ functions with stdlib equivalents
- intrange # range over integer instead of C-style loop (Go 1.22+)
- usestdlibvars # use stdlib constants instead of hardcoded values
- exhaustive # switch statements not covering all enum values
- nolintlint # enforces proper //nolint directive usage
disable:
- lll # line length — handled by gofmt/gofumpt
- prealloc # high false-positive rate; enable only after performance profiling
- wrapcheck # forces wrapping all external errors — too noisy as a default
- err113 # forces package-level sentinel errors — too opinionated, breaks common patterns
- mnd # magic number detector — extremely noisy, flags obvious constants like HTTP 200
- iface # interface pollution detector — too opinionated, not mature enough
- nakedret # naked returns — overlaps with funlen (short functions make naked returns fine)
- noinlineerr # bans `if err := ...; err != nil {}` — this is idiomatic Go
- gocognit # cognitive complexity — redundant with gocyclo + nestif
- cyclop # cyclomatic complexity — redundant with gocyclo
- depguard # import allow/deny lists — requires per-project configuration
- goheader # file header enforcement — project-specific policy
- importas # import alias enforcement — requires per-project configuration
- funcorder # function ordering — too opinionated for a default
- godoclint # godoc validation — overlaps with godot and revive
- varnamelen # variable name length — too opinionated, Go favors short names
- exhaustruct # all struct fields must be set — extremely noisy, breaks zero-value idiom
- gochecknoglobals # no global variables — too strict, many valid uses
- gochecknoinits # no init() functions — too strict, many valid uses
- unparam # unused function parameters — medium false-positive rate with interfaces
- makezero # flags make([]T, n) — noisy, often wrong about intent
- testpackage # forces _test package — valid but too opinionated as a default
- embeddedstructfieldcheck # embedded type placement — minor style, not worth enforcing
- iotamixing # iota in mixed const blocks — very rare issue
- unqueryvet # SELECT * detection — too niche for a default config
- recvcheck # receiver type consistency — overlaps with gocritic
- mirror # bytes/strings mirror patterns — very few real hits
- protogetter # proto field access via getters — only for protobuf users
- spancheck # OpenTelemetry span checks — only for OTel users
- zerologlint # zerolog usage — only for zerolog users
exclusions:
paths:
- vendor$
- third_party$
- testutils$
- examples$
settings:
dupl:
threshold: 100 # lower => stricter (tokens)
errcheck:
check-type-assertions: true
funlen:
lines: 120
statements: 80
goconst:
min-len: 3
min-occurrences: 4
gocyclo:
min-complexity: 13 # strict; lower => stricter
nolintlint:
require-explanation: true
require-specific: true
wsl_v5:
allow-first-in-block: true
allow-whole-block: false
branch-max-lines: 2
formatters:
enable:
- gofmt
- gofumpt
disable:
- gci # import grouping/ordering — gofumpt already handles standard grouping
- goimports # import management — redundant with gofumpt
- golines # line wrapping — too opinionated, can break readability
- swaggo # swaggo comment formatting — only for swaggo users
settings:
gofumpt:
extra-rules: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
[
{
"id": 1,
"name": "nolint-directive-specificity",
"description": "Tests that nolint directives specify the linter name and include a justification — never bare //nolint",
"prompt": "I have a Go function that triggers several lint warnings. I want to suppress them. Write the nolint directives for these cases:\n\n1. A logger.Sync() call where the error is intentionally ignored\n2. A type assertion that is guaranteed safe by a preceding type switch\n3. A function with cyclomatic complexity of 15 that orchestrates 6 subsystems\n4. A table-driven test function that is 200 lines long\n5. A deprecated API call that we can't migrate yet\n\nShow the code with proper suppression directives.",
"trap": "Model uses bare //nolint without specifying the linter name, or omits the justification comment. May also use //nolint at the file level instead of per-line.",
"assertions": [
{
"id": "1.1",
"text": "Every //nolint directive specifies the linter name (e.g., //nolint:errcheck, //nolint:gocyclo) — NO bare //nolint without a linter name"
},
{
"id": "1.2",
"text": "Every //nolint directive includes a justification comment after // (e.g., //nolint:errcheck // fire-and-forget logging)"
},
{
"id": "1.3",
"text": "The type assertion uses //nolint:forcetypeassert with an explanation referencing why the assertion is safe"
},
{
"id": "1.4",
"text": "The long test function uses //nolint:funlen with a justification like 'table-driven test, length proportional to case count'"
},
{
"id": "1.5",
"text": "The cyclomatic complexity suppression uses //nolint:gocyclo with a justification about orchestration"
}
]
},
{
"id": 2,
"name": "nolint-fix-vs-suppress-judgment",
"description": "Tests judgment about when to fix vs when to suppress — security and correctness linters should almost never be suppressed",
"prompt": "My Go codebase has these lint warnings. For each one, should I fix the code or suppress the warning? Explain.\n\n1. `bodyclose: response body not closed` on an HTTP client call\n2. `funlen: function too long (150 lines)` on a table-driven test\n3. `errcheck: error return not checked` on a database query in a request handler\n4. `dupl: duplicate code block` on two similar but intentionally parallel handler functions\n5. `sqlclosecheck: rows not closed` on a database query\n6. `goconst: string 'application/json' repeated 4 times` in test assertions",
"trap": "Model suppresses bodyclose, errcheck on production DB code, or sqlclosecheck — these are real bugs, not style issues. Should only suppress funlen, dupl, and goconst with justifications.",
"assertions": [
{
"id": "2.1",
"text": "Recommends FIXING bodyclose — unclosed HTTP response bodies leak connections, this is a real resource leak"
},
{
"id": "2.2",
"text": "Recommends SUPPRESSING funlen on the table-driven test — length is proportional to test case count, splitting would be worse"
},
{
"id": "2.3",
"text": "Recommends FIXING errcheck on the database query — unchecked errors in production request handlers cause silent failures"
},
{
"id": "2.4",
"text": "Recommends SUPPRESSING dupl on intentional parallel structure — with a justification that the parallel pattern is clearer than abstracting"
},
{
"id": "2.5",
"text": "Recommends FIXING sqlclosecheck — unclosed sql.Rows leak database connections"
},
{
"id": "2.6",
"text": "Recommends SUPPRESSING goconst in tests — extracting 'application/json' to a constant in tests would reduce clarity"
}
]
},
{
"id": 3,
"name": "golangci-yml-version-2-structure",
"description": "Tests knowledge of golangci-lint v2 config structure: version field, linters.enable/disable, formatters section",
"prompt": "Create a .golangci.yml configuration file for a Go project. Enable at least govet, staticcheck, errcheck, and gofumpt. Set the timeout to 5 minutes and configure errcheck to also check type assertions.",
"trap": "Model uses golangci-lint v1 config format (missing version: \"2\", using enable-all/disable-all, missing formatters section, putting gofumpt in linters instead of formatters).",
"assertions": [
{
"id": "3.1",
"text": "Config file has version: \"2\" at the top — golangci-lint v2 requires this field"
},
{
"id": "3.2",
"text": "Linters are listed under linters.enable (not enable-all with exclusions) — explicit listing is the recommended approach"
},
{
"id": "3.3",
"text": "gofumpt is configured under formatters.enable, NOT under linters.enable — formatters are a separate section in v2"
},
{
"id": "3.4",
"text": "errcheck has check-type-assertions: true in linters.settings.errcheck"
},
{
"id": "3.5",
"text": "Timeout is set under run.timeout: 5m"
}
]
},
{
"id": 4,
"name": "linter-categories-correctness-vs-style",
"description": "Tests understanding of linter domains — which linters catch bugs vs which catch style issues",
"prompt": "I'm setting up golangci-lint for a new Go project and can only enable 10 linters due to team constraints. Which 10 should I prioritize and why? Categorize them.",
"trap": "Model prioritizes style linters (revive, godot, misspell) over correctness linters (govet, staticcheck, errcheck, nilerr). May also include deprecated or redundant linters.",
"assertions": [
{
"id": "4.1",
"text": "Includes govet and staticcheck — these are the highest-value correctness linters that catch real bugs"
},
{
"id": "4.2",
"text": "Includes errcheck — unchecked errors are the most common source of silent failures in Go"
},
{
"id": "4.3",
"text": "Prioritizes correctness/safety linters over style linters — bug-finding tools provide more value than formatting preferences"
},
{
"id": "4.4",
"text": "Includes at least one security linter (bodyclose, gosec, or sqlclosecheck) for resource leak prevention"
},
{
"id": "4.5",
"text": "Does NOT include both gocyclo and cyclop (redundant) or both gocognit and gocyclo (overlapping complexity checkers)"
}
]
},
{
"id": 5,
"name": "legacy-codebase-incremental-adoption",
"description": "Tests the new-from-rev strategy for adopting linters on legacy code without drowning in warnings",
"prompt": "We have a large legacy Go codebase with 2000+ lint warnings. We want to adopt golangci-lint but can't fix everything at once. How should we approach this?",
"trap": "Model suggests suppressing all existing warnings with //nolint directives, or disabling linters until the code is clean. Doesn't know about new-from-rev for incremental adoption.",
"assertions": [
{
"id": "5.1",
"text": "Recommends setting issues.new-from-rev (e.g., HEAD~1 or main) in .golangci.yml to only lint new/changed code"
},
{
"id": "5.2",
"text": "Does NOT suggest adding //nolint directives to all 2000+ existing warnings — that's unmaintainable"
},
{
"id": "5.3",
"text": "Suggests gradually cleaning up old code over time while enforcing quality on new code"
},
{
"id": "5.4",
"text": "Suggests running golangci-lint run --fix for auto-fixable issues as a quick first pass"
},
{
"id": "5.5",
"text": "Mentions using parallel sub-agents or batching fixes by linter category (security, error handling, style) to tackle cleanup efficiently"
}
]
},
{
"id": 6,
"name": "interpreting-lint-output-format",
"description": "Tests ability to read lint output format and use the linter name for targeted investigation or suppression",
"prompt": "I ran golangci-lint and got this output:\n\n```\nserver/handler.go:42:10: Error return value of `(*DB).Close` is not checked (errcheck)\nserver/handler.go:55:2: response body must be closed (bodyclose)\nserver/auth.go:12:6: func `validateToken` is unused (unused)\nserver/auth.go:30:1: cyclomatic complexity 17 of func `processAuth` is high (> 13) (gocyclo)\nserver/model.go:5:2: exported type `Model` should have comment or be unexported (revive)\n```\n\nFor each warning, explain what it means and whether I should fix or suppress it.",
"trap": "Model doesn't use the linter name in parentheses to guide its response. May treat all warnings equally instead of recognizing that errcheck and bodyclose are critical while revive is style.",
"assertions": [
{
"id": "6.1",
"text": "Identifies errcheck on DB.Close as a real issue to fix — unchecked database close errors can mask connection problems"
},
{
"id": "6.2",
"text": "Identifies bodyclose as a critical resource leak to fix — not suppress"
},
{
"id": "6.3",
"text": "Identifies unused validateToken as dead code to either remove or fix — not suppress"
},
{
"id": "6.4",
"text": "For gocyclo, evaluates whether processAuth should be refactored or suppressed based on its nature (orchestration function vs genuinely complex logic)"
},
{
"id": "6.5",
"text": "For revive comment warning, correctly identifies it as a style issue that's lower priority than the correctness issues above"
}
]
},
{
"id": 7,
"name": "disabled-linters-with-rationale",
"description": "Tests understanding of which linters should be disabled and why — the recommended config explicitly disables several with reasons",
"prompt": "A colleague wants to enable these linters in our .golangci.yml: exhaustruct, gochecknoglobals, wrapcheck, mnd (magic number detector), and varnamelen. Should we? Explain your reasoning for each.",
"trap": "Model enables all of them without considering that they are intentionally excluded from the recommended config due to being too noisy, too opinionated, or breaking idiomatic Go patterns.",
"assertions": [
{
"id": "7.1",
"text": "Recommends AGAINST exhaustruct — it requires all struct fields to be set, which breaks Go's zero-value idiom and is extremely noisy"
},
{
"id": "7.2",
"text": "Recommends AGAINST gochecknoglobals — there are many valid uses for global variables in Go (loggers, registries, etc.) and a blanket ban is too strict"
},
{
"id": "7.3",
"text": "Recommends AGAINST wrapcheck as a default — it forces wrapping all external errors, which is too noisy and not always appropriate"
},
{
"id": "7.4",
"text": "Recommends AGAINST mnd — magic number detection is extremely noisy, flagging obvious constants like HTTP status codes"
},
{
"id": "7.5",
"text": "Recommends AGAINST varnamelen — Go idiomatically favors short variable names, and this linter conflicts with that philosophy"
}
]
},
{
"id": 8,
"name": "nolintlint-meta-linter",
"description": "Tests knowledge that nolintlint enforces proper nolint directive usage and should be enabled",
"prompt": "I see //nolint directives scattered throughout our Go codebase. Many are bare '//nolint' without specifying which linter or why. How can I enforce proper nolint hygiene automatically?",
"trap": "Model suggests a manual code review process or a custom script instead of enabling the nolintlint linter with require-explanation and require-specific settings.",
"assertions": [
{
"id": "8.1",
"text": "Recommends enabling the nolintlint linter — it automatically enforces nolint directive quality"
},
{
"id": "8.2",
"text": "Configures nolintlint with require-specific: true to require linter names (not bare //nolint)"
},
{
"id": "8.3",
"text": "Configures nolintlint with require-explanation: true to require justification comments"
},
{
"id": "8.4",
"text": "Shows the correct config location: linters.settings.nolintlint in .golangci.yml"
}
]
},
{
"id": 9,
"name": "multiple-nolint-comma-syntax",
"description": "Tests proper syntax for suppressing multiple linters on one line",
"prompt": "I have a line of Go code that triggers both errcheck and gosec warnings. I've confirmed both are false positives in this specific case. How do I suppress both on the same line?",
"trap": "Model uses two separate //nolint directives on the same line, or uses //nolint without comma separation, or stacks directives on consecutive lines for the same code line.",
"assertions": [
{
"id": "9.1",
"text": "Uses comma-separated linter names in a single directive: //nolint:errcheck,gosec — not two separate //nolint directives"
},
{
"id": "9.2",
"text": "Includes a justification comment after the directive explaining why both are false positives"
},
{
"id": "9.3",
"text": "The directive is placed on the same line as the flagged code or the line immediately above it"
}
]
},
{
"id": 10,
"name": "common-config-issues",
"description": "Tests troubleshooting knowledge for golangci-lint: timeout, v1-to-v2 migration, linter-not-found",
"prompt": "I'm getting these errors with golangci-lint:\n1. 'deadline exceeded' when running on our large monorepo\n2. After upgrading to golangci-lint v2, my .golangci.yml throws config errors\n3. 'linter modernize not found' even though I listed it in enable\n\nHow do I fix each?",
"trap": "Model doesn't know about the v2 config migration tool, suggests reinstalling for the linter-not-found issue instead of checking the golangci-lint version, or increases concurrency instead of timeout.",
"assertions": [
{
"id": "10.1",
"text": "For deadline exceeded: recommends increasing run.timeout in .golangci.yml (default is 5m, may need 10m+ for large repos)"
},
{
"id": "10.2",
"text": "For v1 config errors: recommends running golangci-lint migrate to convert the config format to v2"
},
{
"id": "10.3",
"text": "For linter not found: recommends checking the golangci-lint version — modernize requires v2.6.0+ or similar newer version"
},
{
"id": "10.4",
"text": "Mentions golangci-lint linters command to check available linters in the installed version"
}
]
},
{
"id": 11,
"name": "formatter-vs-linter-distinction",
"description": "Tests that formatters (gofumpt, gofmt) are configured in the formatters section, not the linters section, and use the fmt subcommand",
"prompt": "I want to enforce consistent code formatting in my Go project using golangci-lint. I want gofumpt with extra rules. How do I set it up?",
"trap": "Model puts gofumpt in the linters.enable section instead of formatters.enable (v2 distinction), or doesn't mention the golangci-lint fmt subcommand for formatting.",
"assertions": [
{
"id": "11.1",
"text": "Configures gofumpt under formatters.enable, NOT linters.enable — formatters are a separate section in golangci-lint v2"
},
{
"id": "11.2",
"text": "Sets gofumpt extra-rules: true under formatters.settings.gofumpt"
},
{
"id": "11.3",
"text": "Mentions the golangci-lint fmt ./... command for running formatters — separate from golangci-lint run"
},
{
"id": "11.4",
"text": "Notes that gci and goimports are redundant with gofumpt and can be disabled"
}
]
}
]
Linter Reference
golangci-lint v2 uses a .golangci.yml with version: "2" at the project root.
Key sections of .golangci.yml:
- `run` — concurrency, timeout, test inclusion, directory exclusions
- `linters.enable` / `linters.disable` — which linters are active
- `linters.settings` — per-linter thresholds and options
- `formatters` — code formatters (gofmt, gofumpt)
- `issues` — output limits, exclusion rules
To add a linter: add it to linters.enable and optionally configure it in linters.settings.
To disable a linter: move it to linters.disable with a comment explaining why.
Linter Categories
The recommended configuration enables linters across these domains:
| Domain | Linters | Catches |
|---|---|---|
| Correctness | govet, staticcheck, unused, errcheck, errorlint, nilerr, forcetypeassert, copyloopvar, durationcheck, reassign | Bugs, unchecked errors, stdlib misuse |
| Style | gocritic, revive, wsl_v5, whitespace, godot, misspell, dupword, predeclared, errname, asciicheck | Readability, naming, consistency |
| Complexity | gocyclo, nestif, funlen, dupl | Overly complex or duplicated code |
| Performance | perfsprint, unconvert, ineffassign, goconst | Conversions, string ops, dead assigns |
| Security | gosec, bidichk, bodyclose, noctx, containedctx, fatcontext, sqlclosecheck, rowserrcheck | Security issues, resource leaks (HTTP, SQL) |
| Logging | sloglint, loggercheck | Structured log consistency |
| Testing | thelper, paralleltest, testifylint, usetesting | Test hygiene and best practices |
| Modernization | modernize, exptostd, intrange, usestdlibvars, exhaustive, nolintlint | Modern Go idioms, lint hygiene |
| Formatting | gofmt, gofumpt | Code formatting |
All linters are enabled in the recommended .golangci.yml, organized by domain.
Correctness & Safety
- govet — Go's built-in checker: copylocks, printf format mismatches, struct tag validation, context stored in structs, unreachable code, nil dereferences
- staticcheck — Extensive static analysis: deprecated APIs, common mistakes, unnecessary code, simplifications, misuse of standard library
- unused — Detects unused variables, functions, types, and struct fields
- errcheck — Ensures all error returns are checked, including type assertions (configured with
check-type-assertions: true) - nilerr — Detects returning nil error when
erris non-nil (common source of silent failures) - forcetypeassert — Flags type assertions without the comma-ok check (
v := x.(T)instead ofv, ok := x.(T)) - copyloopvar — Detects loop variable copy issues (Go 1.22+)
- errorlint — Enforces correct use of
errors.Is/errors.Asand%wwrapping (Go 1.13+ error wrapping) - durationcheck — Detects
time.Duration * time.Durationmultiplication bugs (e.g.,2 * time.Second * time.Minuteproduces nanoseconds squared, not seconds) - reassign — Detects reassignment of package-level variables outside
init(), which hides state mutations
Style & Readability
- gocritic — Opinionated style checks: unnecessary conversions, range copies, append-assign patterns, redundant code
- revive — Naming conventions for exported types, unexported returns, receiver naming, error naming, stuttered package names
- wsl_v5 — Whitespace and blank line rules for visual grouping and readability
- whitespace — Detects trailing whitespace and unnecessary blank lines in function bodies
- godot — Ensures exported-symbol comments end with a period
- misspell — Catches common English misspellings in identifiers and comments
- predeclared — Flags shadowing of Go built-in identifiers (e.g., naming a variable
len,cap,error) - errname — Enforces error naming conventions: error types suffixed with
Error(e.g.,DecodeError), error variables prefixed withErr(e.g.,ErrNotFound) - dupword — Detects duplicate words in comments and strings (e.g., "the the", "is is") — often copy-paste artifacts
- asciicheck — Flags non-ASCII identifiers that enable homoglyph/trojan source attacks (visually identical but different Unicode codepoints)
Complexity
- gocyclo — Cyclomatic complexity threshold (configured: 13). Functions exceeding this should be split
- nestif — Detects deeply nested if/else chains that harm readability
- funlen — Function length limits (configured: 120 lines, 80 statements)
- dupl — Code duplication detection (configured: 100 token threshold)
Performance
- perfsprint — Suggests faster alternatives to
fmt.Sprintf(e.g.,strconv.Itoainstead offmt.Sprintf("%d", n)) - unconvert — Detects unnecessary type conversions (e.g.,
int(x)whenxis alreadyint) - ineffassign — Detects assignments to variables that are never subsequently read
- goconst — Detects repeated string/number literals that should be extracted to constants (configured: min 3 chars, min 4 occurrences)
Security & Resources
- gosec — Security scanner: SQL injection, hardcoded credentials, weak crypto, path traversal, unsafe usage, and 50+ other rules. The primary SAST tool in the config — never suppress without strong justification.
- bidichk — Detects dangerous bidirectional Unicode sequences (CVE-2021-42574 trojan source attack — code that looks safe but executes differently)
- noctx — Detects HTTP requests sent without
context.Context(prevents proper timeouts and cancellation) - containedctx — Flags
context.Contextstored in struct fields instead of passed as a parameter (anti-pattern per Go docs) - fatcontext — Detects
context.WithValue/WithCancelin loops, creating unbounded context chains that grow each iteration and cause memory leaks - bodyclose — Ensures HTTP response bodies are closed (unclosed bodies leak connections)
- sqlclosecheck — Ensures
sql.Rowsandsql.Stmtare closed after use - rowserrcheck — Ensures
sql.Rows.Err()is checked after iteration
Logging
- sloglint — Enforces consistent
log/slogcode style: proper key-value pairing, message formatting, and level usage - loggercheck — Validates key-value pair formatting for structured loggers (zap, slog, logr) — detects odd numbers of args, missing keys
Testing
- thelper — Ensures test helpers call
t.Helper()so failures report the correct call site - paralleltest — Detects tests and subtests missing
t.Parallel()calls - testifylint — Enforces testify best practices (e.g.,
assert.Equal(t, expected, actual)overassert.True(t, expected == actual)) - usetesting — Suggests
t.Setenv/t.TempDirinstead ofos.Setenv/os.MkdirTempin tests (automatic cleanup, proper isolation)
Modernization & Meta
- modernize — Detects code that can be rewritten using newer Go features (requires golangci-lint v2.6.0+)
- exptostd — Detects
golang.org/x/exp/functions that now have stdlib equivalents (e.g.,slices,maps,cmppackages added in Go 1.21) - intrange — Suggests
range Nover C-stylefor i := 0; i < N; i++loops (Go 1.22+) - usestdlibvars — Replaces hardcoded strings/numbers with stdlib constants (e.g.,
http.MethodGetinstead of"GET") - exhaustive — Ensures switch statements on enum types cover all possible values
- nolintlint — Enforces proper
//nolintdirective usage: requires linter name and justification comment (configured withrequire-explanationandrequire-specific)
Formatting
Formatters run via golangci-lint fmt ./...:
- gofmt — Standard Go formatter (canonical formatting)
- gofumpt — Stricter formatter with extra rules (configured with
extra-rules: true): consistent empty lines, grouped imports, simplified code patterns
Nolint Directives
Syntax
//nolint:lintername // justification explaining why this suppression is neededPlace the directive on the same line as the flagged code, or on the line immediately above it.
Rules
1. MUST specify the linter name — bare //nolint suppresses all linters on that line and makes it impossible to track what is being suppressed 2. MUST add a justification comment — future readers (and your future self) need to understand why 3. The `nolintlint` linter enforces both rules — it will flag bare //nolint and missing reasons 4. MUST fix the root cause before suppressing — only suppress after confirming the issue is a false positive or an intentional pattern
Examples
// Specific linter with reason
//nolint:errcheck // fire-and-forget logging, error not actionable
_ = logger.Sync()
// Type assertion is safe because preceding type switch guarantees the type
v := x.(MyType) //nolint:forcetypeassert // guaranteed by type switch on line 42
// Orchestration function has inherent complexity
//nolint:gocyclo // orchestration function coordinating 8 subsystems
func orchestrate() error {
// Table-driven test with many cases
//nolint:funlen // table-driven test, length is proportional to case count
func TestParser(t *testing.T) {
// Intentional parallel structure is clearer than abstracting
//nolint:dupl // intentional parallel structure for readabilityMultiple Linters
Suppress multiple linters on one line with comma separation:
//nolint:errcheck,gosec // fire-and-forget in test helperWhen to Suppress vs. When to Fix
Fix (almost always):
errcheck— check the error, even if just logging itgovet— these are usually real bugsstaticcheck— deprecated API usage, logic errorsbodyclose,sqlclosecheck— resource leaks are real issues
Suppress (with justification):
funlen— table-driven tests with many casesgocyclo— orchestration functions where splitting would obscure the flowdupl— intentional parallel structure that is clearer than an abstractionexhaustive— when a default case intentionally handles remaining valuesgoconst— when extracting to a constant would reduce clarity (e.g., test assertions)
Never suppress without strong justification:
- Security linters (
bodyclose,sqlclosecheck,rowserrcheck) — these catch real resource leaks errcheckon production code paths — unchecked errors cause silent failures
Related skills
FAQ
Which linters does golang-lint enable?
golang-lint enables govet, staticcheck, unused, errcheck, errorlint, and nilerr in a golangci-lint version 2 config. Analysis includes test files with a 5-minute timeout and concurrency set to 4.
Does golang-lint check Go test files?
golang-lint sets tests: true in golangci-lint run config so test files are analyzed alongside production code. Use it before commit to surface unchecked errors and deprecated API usage.
Is Golang Lint safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.