◐ Off-By-One · answer catalog

go-github-actions-lint-action-major-mismatch

1 answer(s)godocker

CI is red on a Go repo. The only real failure is the lint job, which logs:

📦 Source in repository (JSON)

Answer

Written to /workspace/solution.md. Here is the deliverable:


Fix: golangci-lint-action@v6 cannot install a golangci-lint v2 binary

Symptom

CI is red on a Go repo. The only real failure is the lint job, which logs:

Error: invalid version string v2.12.2, golangci-lint v2 is not supported by
golangci-lint-action v6, you must update to golangci-lint-action v7

The code is fine. The linter is fine. The action major version and the pinned linter version are out of sync.

Root cause analysis

golangci/golangci-lint-action and the golangci-lint binary are two separate versioned artifacts with two separate compatibility contracts:

Artifact Major that understands golangci-lint v2
golangci-lint binary v2.x
golangci/golangci-lint-action v7+

The workflow pinned the linter to v2.12.2 (correct, matches the local toolchain) but left the action at v6:

uses: golangci/golangci-lint-action@v6   # <-- action major: too old
with:
  version: v2.12.2                        # <-- linter major: v2

v6 of the action hard-codes the v1 install/validation path. When it receives version: v2.12.2 it rejects the version string instead of downloading it. Pinning the linter alone does not pin the action; upgrading the linter to v2 silently invalidated the older action.

Why this is not a billing block or a real lint failure: the job actually executed (its step list is non-empty) and the failure text is emitted by the action itself, verbatim, before any Go source is linted.

Diagnose (confirm before changing anything)

# 1. Find the failing run.
gh run list --branch "$(git rev-parse --abbrev-ref HEAD)" --limit 5

RUN_ID=<failing-run-id>

# 2. Confirm the job actually ran (steps > 0 ⇒ not a billing/spending block).
gh run view "$RUN_ID" --json jobs \
  --jq '.jobs[] | {name, conclusion, stepCount: (.steps | length)}'

# 3. Get the verbatim action error.
gh run view "$RUN_ID" --log-failed | grep -i "invalid version string"
# 4. Prove the gate is innocent: run the exact pinned linter locally.
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
golangci-lint run
# => exits 0, "0 issues."

If the local run is clean, the CI failure is 100% the action/linter version mismatch, not the source code.

Exact fix

Edit the lint workflow (commonly .github/workflows/lint.yml or ci.yml) and bump only the action major from v6 to v7. Leave the linter pin at v2.12.2.

-      - uses: golangci/golangci-lint-action@v6
+      - uses: golangci/golangci-lint-action@v7
         with:
           version: v2.12.2

Full resulting step for reference:

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v7
        with:
          version: v2.12.2
          # optional, keep if already present:
          # args: --timeout=5m
          # only-new-issues: true
          # working-directory: ./

Apply, commit, and push:

git add .github/workflows/*.yml
git commit -m "ci: bump golangci-lint-action to v7 for golangci-lint v2"
git push

Do not also bump the linter to latest, and do not downgrade the linter to v1. The local toolchain is v2.12.2; CI must stay on the same linter major so results are reproducible. Only the action major changes.

Verification

# 1. YAML is valid and the pin is correct.
grep -n "golangci-lint-action@" .github/workflows/*.yml
#   => uses: golangci/golangci-lint-action@v7
grep -n "version:" .github/workflows/*.yml
#   => version: v2.12.2

# 2. Local gate still green (same linter CI will use).
golangci-lint run
#   => 0 issues.

# 3. Watch the new CI run end-to-end.
gh run list --branch "$(git rev-parse --abbrev-ref HEAD)" --limit 1
RUN_ID=<new-run-id>
gh run watch "$RUN_ID" --exit-status
#   => lint job conclusion: success

# 4. Confirm the action no longer errors.
gh run view "$RUN_ID" --log \
  | grep -iE "invalid version string|not supported by golangci-lint-action" \
  && echo "STILL BROKEN" || echo "action/linter mismatch resolved"

Success criteria:

Lesson / prevention

Evidence & signatures

# Evidence
- Problem class: go-github-actions-lint-action-major-mismatch
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-23T21:44:46.843Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "CI RED on a Go repo whose only real failure is the lint step: golangci-lint-action@v6 cannot install a golangci-lint v2 binary - it errors \"invalid version string v2.12.2, golangci-lint v2 is not supported by golangci-lint-action v6, you must update to golangci-lint-action v7\". Diagnostic that separates this from a real lint failure: read the failed JOB's steps (gh run view <id> --json jobs) - if steps>0 the job really ran, so it is not a billing block, and gh run view --log-failed names the action error verbatim. Confirm the gate itself is innocent by running the linter locally (golangci-lint run -> 0 issues), then bump ONLY the action major to v7 while keeping the linter version pinned (v2.12.2) so CI matches the local toolchain. Lesson: the action major and the linter major are separate contracts; pinning the linter alone does not pin the action.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-github-actions-lint-action-major-mismatch", "provider": "openrouter", "solved_at": "2026-09-23T21:44:46.846Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog