go-lint-setup — scaffold, adopt, or debug golangci-lint v2
Bundled references/ is at the plugin root (beside skills/, two levels above this file) — not under this skill. Read references/golangci.v2.yml as ${CLAUDE_PLUGIN_ROOT}/references/golangci.v2.yml on Claude Code, or ../../references/golangci.v2.yml from this skill's directory, or Glob for the installed references/golangci.v2.yml (host-agnostic).
Scaffold the plugin's reference golangci-lint v2 config into a repo that has none, or adopt or
debug an existing config already in the repo — schema questions, migration, which linters,
//nolint, exclusions. Scaffolding is a single interaction.
Asked to set up or scaffold a config → Steps 1–3 below. Asked about an existing config (rejected
keys, migration, which linters, //nolint, exclusions) → skip to Adopting or debugging an
existing config; do not write a file.
Steps:
- Check for an existing config —
.golangci.yml, .golangci.yaml, .golangci.toml,
.golangci.json. If one exists, do not overwrite it: show how it differs from the reference
and ask before changing anything. If it's a v1 config (no version key and/or an
enable-all/top-level linters: list), warn that v1 will not parse under golangci-lint v2 and
offer to migrate — the supported path is golangci-lint migrate (in-place, keeps a .bck backup,
drops comments), not a hand-port.
- Write the config below to
.golangci.yml (or the path given in $ARGUMENTS).
- Report how to run it:
golangci-lint run, and golangci-lint run --fix for the auto-fixable
findings (modernize + the formatters). Suggest pinning an exact golangci-lint version in CI in
one place (the action's version: input) with an automated bump PR — see Adopting or debugging
an existing config below; don't invent a version number here, point at the releases page.
Config to write (mirrors references/golangci.v2.yml — keep the two in sync):
version: "2"
linters:
default: standard
enable:
- modernize
- errorlint
- exhaustive
- bodyclose
- rowserrcheck
- sqlclosecheck
- noctx
- contextcheck
- containedctx
- perfsprint
- usetesting
- nolintlint
- revive
formatters:
enable:
- gofumpt
- goimports
For what each linter does and why, see Adopting or debugging an existing config below, or the
inline comments in references/golangci.v2.yml.
Adopting or debugging an existing config
golangci-lint v2 (Mar 2025) changed the config schema from v1 — a v1 config will not parse:
- Top-level
version: "2" is required.
linters.default: standard | all | none | fast selects the base set (no more enable-all).
standard = errcheck, govet, ineffassign, staticcheck, unused.
- Formatters moved to their own
formatters: section (gofmt/gofumpt/goimports are no longer
"linters"), with their settings under formatters.settings. golangci-lint fmt runs that section.
- Exclusions moved under
linters: v1's issues.exclude-rules → linters.exclusions.rules,
and issues.exclude-dirs/exclude-files → linters.exclusions.paths. linters-settings split
into linters.settings + formatters.settings. A config that still uses the old key names —
issues:, linters-settings:, enable-all — is v1 and needs golangci-lint migrate (Step 1
above), not a hand-port; migrate rewrites in place, keeps a .golangci.bck.yml backup, and
takes --format {yml,yaml,toml,json} — it drops comments and unknown/deprecated keys, so re-add
comments and diff the result.
Common breakage when bumping the pin: run --fix first, then either land the leftover findings
or add an explicit linters.exclusions.rules entry with a reason. If the pinned build rejects a
linter name from the reference config, the pin is too old — bump it rather than deleting the linter.
Adopting modernize: it is the single highest-leverage linter in the reference set — it
operationalizes most go-idioms rules on the same engine as gopls/go fix, so the plugin's advice
stays consistent with the toolchain. As of Go 1.26 the rewritten go fix ./... runs that same
modernizer suite from the toolchain itself; keep modernize in golangci-lint so CI enforces it
reproducibly against the pinned version rather than whatever toolchain a developer happens to have.
errorlint pairs with it in the reference config (%w + errors.Is/AsType discipline — see
go-errors).
Go 1.27 needs golangci-lint ≥ v2.13.0 (released 2026-08-19 — the same day as Go 1.27 itself) for
Go 1.27 support; anything v2.12.x or earlier predates it — a compatibility floor, not a pin (see
Discipline once adopted). Source: https://golangci-lint.run/docs/product/changelog/#2130.
Discipline once adopted
- Pin an exact version in CI, in exactly one place — and keep the pin moving. Upstream's own
recommendation is a specific release, not
latest: a new release can add or retune linters and
turn every build red at once, with no code change to blame. Put the version in a single source of
truth — the golangci/golangci-lint-action version: input (it also caches, and beats a plain
binary install) or the install script's tag — never copied across several workflows and Makefiles.
Then let Renovate/Dependabot raise the bump as its own PR, so the version stays current and
every rule-set change arrives reviewable. This skill deliberately names no blessed version; read
the changelog for the current line.
- Install the release binary, not from source. Upstream states that
go install/go get, the
tools pattern, and tool directives "aren't guaranteed to work" — they compile golangci-lint with
whatever local Go version is around. Use the binary, the action, or the Docker image, from a
release built with Go ≥ the module's toolchain (1.26+) so it can parse the language version.
- Suppress narrowly, and say why.
//nolint:errcheck // best-effort close on a read-only handle
— never a bare //nolint (it disables every linter on that line) and never a blanket
file-level disable where a linters.exclusions.rules entry with a path pattern is the honest
answer. nolintlint enforces the specific-and-explained form.
Sources
Decomposition inspired by samber/cc-skills-golang (MIT © 2026 Samuel Berthe); rules grounded in the sources above.
1---2name: go-lint-setup3description: Scaffold, adopt, or debug the golangci-lint v2 config in a Go repo. This skill should be used when the user runs `/go-lint-setup`, asks to "set up", "scaffold", "add" or "bootstrap" golangci-lint or a `.golangci.yml`, or asks why golangci-lint v2 rejects a config, how to migrate a v1 config, which linters the default set enables, how to adopt modernize/errorlint in an existing repo, what `golangci-lint fmt` does, how to write a `linters.exclusions` rule, or how to suppress a finding with `//nolint`. Writes the reference v2 config (modernize + stack linters); never overwrites an existing one unprompted. Go only.4---56# go-lint-setup — scaffold, adopt, or debug golangci-lint v278> **Bundled `references/` is at the plugin root** (beside `skills/`, two levels above this file) — *not* under this skill. Read `references/golangci.v2.yml` as `${CLAUDE_PLUGIN_ROOT}/references/golangci.v2.yml` on Claude Code, or `../../references/golangci.v2.yml` from this skill's directory, or Glob for the installed `references/golangci.v2.yml` (host-agnostic).910Scaffold the plugin's reference **golangci-lint v2** config into a repo that has none, or adopt or11debug an existing config already in the repo — schema questions, migration, which linters,12`//nolint`, exclusions. Scaffolding is a single interaction.1314Asked to set up or scaffold a config → Steps 1–3 below. Asked about an existing config (rejected15keys, migration, which linters, `//nolint`, exclusions) → skip to *Adopting or debugging an16existing config*; do not write a file.1718Steps:19201. **Check for an existing config** — `.golangci.yml`, `.golangci.yaml`, `.golangci.toml`,21 `.golangci.json`. If one exists, do **not** overwrite it: show how it differs from the reference22 and ask before changing anything. If it's a **v1** config (no `version` key and/or an23 `enable-all`/top-level `linters:` list), warn that v1 will not parse under golangci-lint v2 and24 offer to migrate — the supported path is `golangci-lint migrate` (in-place, keeps a `.bck` backup,25 drops comments), not a hand-port.262. **Write** the config below to `.golangci.yml` (or the path given in `$ARGUMENTS`).273. **Report how to run it:** `golangci-lint run`, and `golangci-lint run --fix` for the auto-fixable28 findings (`modernize` + the formatters). Suggest pinning an exact `golangci-lint` version in CI in29 one place (the action's `version:` input) with an automated bump PR — see *Adopting or debugging30 an existing config* below; don't invent a version number here, point at the releases page.3132Config to write (mirrors `references/golangci.v2.yml` — keep the two in sync):3334```yaml35version: "2"36linters:37 default: standard38 enable:39 - modernize40 - errorlint41 - exhaustive42 - bodyclose43 - rowserrcheck44 - sqlclosecheck45 - noctx46 - contextcheck47 - containedctx48 - perfsprint49 - usetesting50 - nolintlint51 - revive52formatters:53 enable:54 - gofumpt55 - goimports56```5758For what each linter does and why, see *Adopting or debugging an existing config* below, or the59inline comments in `references/golangci.v2.yml`.6061## Adopting or debugging an existing config6263golangci-lint **v2** (Mar 2025) changed the config schema from v1 — **a v1 config will not parse**:6465- Top-level `version: "2"` is required.66- `linters.default: standard | all | none | fast` selects the base set (no more `enable-all`).67 `standard` = errcheck, govet, ineffassign, staticcheck, unused.68- **Formatters moved to their own `formatters:` section** (gofmt/gofumpt/goimports are no longer69 "linters"), with their settings under `formatters.settings`. `golangci-lint fmt` runs that section.70- **Exclusions moved under `linters`**: v1's `issues.exclude-rules` → `linters.exclusions.rules`,71 and `issues.exclude-dirs`/`exclude-files` → `linters.exclusions.paths`. `linters-settings` split72 into `linters.settings` + `formatters.settings`. A config that still uses the old key names —73 `issues:`, `linters-settings:`, `enable-all` — is v1 and needs `golangci-lint migrate` (Step 174 above), not a hand-port; `migrate` rewrites in place, keeps a `.golangci.bck.yml` backup, and75 takes `--format {yml,yaml,toml,json}` — it drops comments and unknown/deprecated keys, so re-add76 comments and diff the result.7778**Common breakage when bumping the pin:** run `--fix` first, then either land the leftover findings79or add an explicit `linters.exclusions.rules` entry with a reason. If the pinned build rejects a80linter name from the reference config, the pin is too old — bump it rather than deleting the linter.8182**Adopting `modernize`:** it is the single highest-leverage linter in the reference set — it83operationalizes most `go-idioms` rules on the same engine as gopls/`go fix`, so the plugin's advice84stays consistent with the toolchain. As of **Go 1.26** the rewritten `go fix ./...` runs that same85modernizer suite from the toolchain itself; keep `modernize` in golangci-lint so CI enforces it86reproducibly against the pinned version rather than whatever toolchain a developer happens to have.87`errorlint` pairs with it in the reference config (`%w` + `errors.Is`/`AsType` discipline — see88`go-errors`).8990**Go 1.27 needs golangci-lint ≥ v2.13.0** (released 2026-08-19 — the same day as Go 1.27 itself) for91Go 1.27 support; anything v2.12.x or earlier predates it — a compatibility floor, not a pin (see92*Discipline once adopted*). Source: <https://golangci-lint.run/docs/product/changelog/#2130>.9394### Discipline once adopted9596- **Pin an exact version in CI, in exactly one place — and keep the pin moving.** Upstream's own97 recommendation is a specific release, not `latest`: a new release can add or retune linters and98 turn every build red at once, with no code change to blame. Put the version in a single source of99 truth — the `golangci/golangci-lint-action` `version:` input (it also caches, and beats a plain100 binary install) or the install script's tag — never copied across several workflows and Makefiles.101 Then let Renovate/Dependabot raise the bump as its own PR, so the version stays current *and*102 every rule-set change arrives reviewable. This skill deliberately names no blessed version; read103 the changelog for the current line.104- **Install the release binary, not from source.** Upstream states that `go install`/`go get`, the105 tools pattern, and `tool` directives "aren't guaranteed to work" — they compile golangci-lint with106 whatever local Go version is around. Use the binary, the action, or the Docker image, from a107 release built with Go ≥ the module's toolchain (1.26+) so it can parse the language version.108- **Suppress narrowly, and say why.** `//nolint:errcheck // best-effort close on a read-only handle`109 — never a bare `//nolint` (it disables every linter on that line) and never a blanket110 file-level disable where a `linters.exclusions.rules` entry with a path pattern is the honest111 answer. `nolintlint` enforces the specific-and-explained form.112113## Sources114- golangci-lint docs — <https://golangci-lint.run/docs/>; v1→v2 migration guide (`migrate`, key moves) — <https://golangci-lint.run/docs/product/migration-guide/>115- v2 announcement (`fmt`, `formatters`) — <https://ldez.github.io/blog/2025/03/23/golangci-lint-v2/>116- `modernize` — <https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize>117118---119*Decomposition inspired by [`samber/cc-skills-golang`](https://github.com/samber/cc-skills-golang) (MIT © 2026 Samuel Berthe); rules grounded in the sources above.*