Skip to content

ci: get the lint job running again on golangci-lint v2 - #2

Merged
joyjit merged 1 commit into
mainfrom
ci-golangci-v2
Aug 25, 2026
Merged

ci: get the lint job running again on golangci-lint v2#2
joyjit merged 1 commit into
mainfrom
ci-golangci-v2

Conversation

@joyjit

@joyjit joyjit commented Aug 25, 2026

Copy link
Copy Markdown
Owner

The lint job has never passed on this repo. It was not failing on the code — it never reached the code.

Why it was broken

golangci/golangci-lint-action@v6 only supports golangci-lint v1, so version: latest resolved to v1.64.8, which is built with Go 1.24 and refuses to start against a go.mod targeting 1.25.0:

can't load config: the Go language version (go1.24) used to build
golangci-lint is lower than the targeted Go version (1.25.0)

What changed

  • Action v6 → v9, golangci-lint v1.64.8 → v2.13.1, pinned rather than latest (floating is exactly what picked an incompatible version without anyone noticing).
  • .golangci.yml migrated to the v2 format via golangci-lint migrate, comments restored by hand. Same effective linter set: errcheck, govet, ineffassign, staticcheck and unused are v2 defaults, and gofmt/goimports move to the new formatters section.

What the linter found once it ran

16 issues. Two were real:

  • internal/api/firmware.goupgradeInProgress was dead code; both real callers hold s.mu and call runningUpgradeLocked directly. Removed, with its note about the fleet-wide one-at-a-time rule moved onto the helper that survives.
  • internal/driver/wax/pin_test.go — the path-stability check compared two identical calls inline, which staticcheck reads as a tautology (SA4000). Same check, held in variables, so what it is asserting is legible.

The rest are false positives against deliberate choices, each annotated at the site: the sanitizer's 0644 fixtures (committed, no secrets by construction), the detached upgrade goroutine (it must outlive the request), and the auth cookies — which already set HttpOnly and SameSite, with Secure deliberately conditional so login still works over plain HTTP on a LAN.

The TLS one worth reading

gosec flagged the certificate-pinning setup (G123): VerifyPeerCertificate is skipped on resumed TLS sessions, so a resumed connection could dodge the pin check. It cannot happen here — Go only resumes when tls.Config.ClientSessionCache is set, net/http never sets one (checked the 1.25 stdlib), and neither do we, so every connection is a full handshake. That reasoning is now written next to the code, along with what to do if a session cache is ever added: move the check to VerifyConnection, which does run on resumed handshakes.

Taint analysis is off, deliberately

G703 and G706 are disabled. They are not deterministic in v2.13.1 — on identical source with a cold cache, G703 reported three findings in internal/api/history.go on roughly one run in three and none on the others. A check that fails at random is worse than no check. What they look for is already enforced by core.ValidateDeviceName (a single safe path element, no separators, no ..) and covered by the traversal cases in internal/core/config_test.go.

Verification

On the build host, in containers:

  • 10 consecutive cold golangci-lint run invocations: 0 issues each. (Before the exclusion, the same tree flipped between 0 and 3.)
  • go build ./..., go vet ./..., gofmt -l . clean, go test ./... all packages pass.

No access point was contacted.

The lint job has never passed. golangci-lint-action@v6 only knows
golangci-lint v1, so `version: latest` resolved to v1.64.8, which is
built with Go 1.24 and refuses to start against a go.mod targeting
1.25.0. It failed before reaching any code.

Move to golangci-lint-action@v9 with golangci-lint v2.13.1, pinned
rather than `latest` — floating is what silently picked a version
incompatible with the toolchain. .golangci.yml is migrated to the v2
format (errcheck, govet, ineffassign, staticcheck and unused are v2
defaults; gofmt and goimports move to `formatters`), keeping the same
effective linter set.

With the linter actually running, it found 16 issues. Two were real:

- api/firmware.go: upgradeInProgress was dead code. Both callers hold
  s.mu and use runningUpgradeLocked directly. Removed, and its note on
  the one-AP-at-a-time rule moved to the helper that survives.
- driver/wax/pin_test.go: the path-stability check compared two
  identical calls inline, which staticcheck reads as a tautology.
  Same check, via variables, so the intent is legible.

The rest are false positives against deliberate choices, each marked
with the reason at the site: the sanitizer's 0644 fixtures, the
detached upgrade goroutine, and the auth cookies, whose Secure flag is
conditional so that login still works over plain HTTP on a LAN. gosec
also flags the pinned-TLS setup for a resumption bypass that cannot
happen here — Go only resumes when ClientSessionCache is set, and
neither net/http nor we set one, so the pin is checked on every
handshake. That is now written down next to the code.

gosec's two taint-analysis rules are switched off. They are not
deterministic in v2.13.1: on identical source with a cold cache, G703
reported three findings in api/history.go on roughly one run in three
and none on the others. What they check is already enforced by
core.ValidateDeviceName and covered by tests. Ten consecutive cold
runs are now clean.
@joyjit
joyjit merged commit fb44fc4 into main Aug 25, 2026
2 checks passed
@joyjit
joyjit deleted the ci-golangci-v2 branch August 25, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant