If you use this in GitHub Actions:

1- name: golangci-lint
2  uses: golangci/golangci-lint-action@v9
3  with:
4    version: latest

you are absolutely right to be cautious: latest can move to a new golangci-lint release that adds support for a new Go minor while your workflow, runner, or repo expectations are still pinned to older assumptions.

This guide gives you:

  • a practical version pairing table (Go ↔ golangci-lint ↔ action),
  • safe version ranges you can reuse,
  • and many copy/paste workflow examples.

If you want a robust setup today:

  1. Pin the action major (@v9) and pin a golangci-lint minor line (v2.6.x, v2.8.x, etc.) instead of latest.
  2. Use actions/setup-go@v6 before golangci-lint.
  3. Test at least stable and oldstable Go in CI matrix.
  4. Keep in mind golangci-lint policy: supports the two latest Go minor versions.

Why version: latest can surprise you

golangci-lint-action accepts version: latest, but that means each CI run can pick newer golangci-lint binaries as they are released. Newer binaries can include parser/toolchain behaviour changes and new Go-version support windows. Great for fast adoption, risky for deterministic CI.

In short:

  • latest = convenience,
  • pinned version/range = reproducibility.

Compatibility facts you should anchor on

1) golangci-lint-action@v9 runtime and action compatibility

From action docs:

  • v9.0.0 requires Node.js runtime node24.
  • v8.0.0 works with golangci-lint >= v2.1.0.
  • v7.0.0 supports golangci-lint v2 only.
  • v4.0.0+ requires an explicit actions/setup-go step.

So for @v9, pair it with setup-go@v6 (also node24 generation).

2) actions/setup-go@v6 runtime note

setup-go@v6 moved from Node 20 to Node 24 and calls out runner compatibility requirements in its README/release notes.

3) golangci-lint Go support policy

golangci-lint FAQ says it tracks Go team policy (the 2 latest minor versions) and that practical support depends on the Go version used to build golangci-lint and linter ecosystem readiness.

4) recent Go support milestones in golangci-lint releases

From changelog/release notes:

  • Go 1.22 support: v1.56.0 line.
  • Go 1.23 support: v1.60.1.
  • Go 1.24 support: v1.64.2.
  • Go 1.25 support: v2.4.0.
  • Go 1.26 support: v2.9.0.

Legend:

  • Minimum known support = first release explicitly mentioning support.
  • Recommended today = practical pin choice for modern CI.
  • Keep golangci-lint-action on @v9 unless you have a legacy constraint.
Target Go versionMinimum golangci-lint with explicit supportRecommended golangci-lint pin styleAction majorsetup-go
Go 1.22v1.56.0Prefer v2 line if possible (for modern action/docs), otherwise late v1 for legacy reposv9v6
Go 1.23v1.60.1v2.x pinned (v2.4+)v9v6
Go 1.24v1.64.2v2.x pinned (v2.4+)v9v6
Go 1.25v2.4.0v2.4+ pinned (v2.4.x / v2.5.x / v2.6.x)v9v6
Go 1.26v2.9.0v2.9+ pinned (v2.9.x)v9v6

Version ranges you can adopt

If you like semver-ish policy language in docs/team standards, these are good operational ranges:

  • For Go 1.22–1.24 repos: use golangci-lint >=2.4.0, <3.0.0 and pin exact patch in workflow.
  • For Go 1.25 repos: use golangci-lint >=2.4.0, <3.0.0 (pin exact).
  • For Go 1.26 repos: use golangci-lint >=2.9.0, <3.0.0 (pin exact).

Copy/paste workflow examples

1) Most deterministic (single Go, exact lint version)

 1name: lint
 2
 3on:
 4  pull_request:
 5  push:
 6    branches: [ main ]
 7
 8jobs:
 9  golangci:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v4
13
14      - uses: actions/setup-go@v6
15        with:
16          go-version: '1.25'
17
18      - name: golangci-lint
19        uses: golangci/golangci-lint-action@v9
20        with:
21          version: v2.6.1
22          args: --timeout=5m
 1name: lint
 2
 3on:
 4  pull_request:
 5  push:
 6    branches: [ main ]
 7
 8jobs:
 9  golangci:
10    runs-on: ubuntu-latest
11    strategy:
12      fail-fast: false
13      matrix:
14        go-version: [stable, oldstable]
15
16    steps:
17      - uses: actions/checkout@v4
18
19      - uses: actions/setup-go@v6
20        with:
21          go-version: ${{ matrix.go-version }}
22
23      - name: golangci-lint
24        uses: golangci/golangci-lint-action@v9
25        with:
26          version: v2.9.0
27          args: --timeout=5m

3) If you insist on latest (guardrails)

 1name: lint
 2
 3on:
 4  schedule:
 5    - cron: '0 3 * * *'
 6  pull_request:
 7
 8jobs:
 9  golangci:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v4
13
14      - uses: actions/setup-go@v6
15        with:
16          go-version: 'stable'
17
18      - name: golangci-lint
19        uses: golangci/golangci-lint-action@v9
20        with:
21          version: latest
22          args: --timeout=5m

Use this only if your team accepts occasional churn; combine with scheduled runs so breakage appears before business hours.

4) Go 1.22 pinned example

1- uses: actions/setup-go@v6
2  with:
3    go-version: '1.22'
4
5- name: golangci-lint
6  uses: golangci/golangci-lint-action@v9
7  with:
8    version: v2.4.0
9    args: --timeout=5m

5) Go 1.23 pinned example

1- uses: actions/setup-go@v6
2  with:
3    go-version: '1.23'
4
5- name: golangci-lint
6  uses: golangci/golangci-lint-action@v9
7  with:
8    version: v2.4.0
9    args: --timeout=5m

6) Go 1.24 pinned example

1- uses: actions/setup-go@v6
2  with:
3    go-version: '1.24'
4
5- name: golangci-lint
6  uses: golangci/golangci-lint-action@v9
7  with:
8    version: v2.6.1
9    args: --timeout=5m

7) Go 1.25 pinned example

1- uses: actions/setup-go@v6
2  with:
3    go-version: '1.25'
4
5- name: golangci-lint
6  uses: golangci/golangci-lint-action@v9
7  with:
8    version: v2.6.1
9    args: --timeout=5m

8) Go 1.26 pinned example

1- uses: actions/setup-go@v6
2  with:
3    go-version: '1.26'
4
5- name: golangci-lint
6  uses: golangci/golangci-lint-action@v9
7  with:
8    version: v2.9.0
9    args: --timeout=5m

9) Use .golangci-lint-version file

.golangci-lint-version

1v2.9.0

Workflow:

1- name: golangci-lint
2  uses: golangci/golangci-lint-action@v9
3  with:
4    version-file: .golangci-lint-version
5    args: --timeout=5m

10) Reusable workflow pattern for org-wide consistency

 1name: reusable-golangci
 2
 3on:
 4  workflow_call:
 5    inputs:
 6      go-version:
 7        required: true
 8        type: string
 9      golangci-version:
10        required: true
11        type: string
12
13jobs:
14  lint:
15    runs-on: ubuntu-latest
16    steps:
17      - uses: actions/checkout@v4
18      - uses: actions/setup-go@v6
19        with:
20          go-version: ${{ inputs.go-version }}
21      - uses: golangci/golangci-lint-action@v9
22        with:
23          version: ${{ inputs.golangci-version }}
24          args: --timeout=5m

Caller example:

1jobs:
2  call-lint:
3    uses: your-org/your-repo/.github/workflows/reusable-golangci.yml@main
4    with:
5      go-version: '1.25'
6      golangci-version: 'v2.6.1'

Migration guidance (if you are currently using version: latest)

  1. Keep action at @v9.
  2. Replace version: latest with an exact version (v2.9.0 for Go 1.26, v2.6.1 for Go 1.24/1.25, etc.).
  3. Add a weekly scheduled workflow that temporarily runs latest to preview upcoming breaks.
  4. Bump pinned version intentionally in PRs.

Common pitfalls

  • Forgetting the explicit setup-go step (required by modern action versions).
  • Using unquoted Go versions (1.22 parsed as 1.2 by YAML).
  • Upgrading action major without runner compatibility checks.
  • Using latest on critical branches without canary/scheduled workflows.


If you want, you can keep this short policy in your repo:

We pin golangci/golangci-lint-action@v9, use actions/setup-go@v6, pin golangci-lint to an exact v2.x tag, and update it intentionally.