Skip to content

CI: gofmt/lint/tidy/vulncheck gates, enable -race, bump to go1.26.8 - #7

Merged
naliyi merged 8 commits into
mainfrom
ci-gofmt-lint-race-vulncheck
Sep 3, 2026
Merged

CI: gofmt/lint/tidy/vulncheck gates, enable -race, bump to go1.26.8#7
naliyi merged 8 commits into
mainfrom
ci-gofmt-lint-race-vulncheck

Conversation

@naliyi

@naliyi naliyi commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #6

Summary

Brings ci.yml up to general Go-library practice and this org's own established pattern (ncli already uses go-version-file and -race):

  • go-version-file: go.mod instead of a hardcoded "1.25"
  • go test -race — nmilat has already shipped one real goroutine-leak bug (0.2.5); this PR's third commit fixes a second one that -race caught for the first time, which is exactly the class of bug this gate exists for
  • New gates: gofmt -l, go mod tidy diff check, golangci-lint (separate job, full repo, blocking), govulncheck (separate job, blocking)
  • Explicit permissions: contents: read, concurrency: cancel-in-progress

To make those gates land clean rather than immediately red:

  • Fixed all 155 pre-existing golangci-lint findings repo-wide (verified with --max-issues-per-linter=0 --max-same-issues=0 — the default caps were silently hiding the true count, which is more than double what I originally quoted)
  • Fixed a real intermittent data race: httptest.Server.Close() doesn't wait for a websocket session's own goroutines to exit (gorilla/websocket hijacks the connection, so httptest's own connection-tracking WaitGroup considers it done the instant it's hijacked) — a leftover session from one test could still be running when the next test started. Added waitForSessionCount and wired it into every test that dials a websocket. Verified with 4 independent uncached -race runs post-fix.
  • Bumped go.mod to 1.26.8, clearing all 13 stdlib CVEs govulncheck was flagging (toolchain-version issues, not nmilat bugs)

Also caught and fixed two real (non-cosmetic) bugs while fixing lint findings: handler_cache.go silently accepted malformed cache-options JSON instead of rejecting it, and store.go's event-delete path had an empty if err != nil {} branch on a Put() failure instead of propagating it.

Commit-by-commit breakdown is in the individual commit messages.

Test plan

  • gofmt -l . clean
  • golangci-lint run --max-issues-per-linter=0 --max-same-issues=0 ./... — 0 issues
  • govulncheck ./... — 0 vulnerabilities (reachable)
  • go build ./..., go vet ./... clean
  • go test -race ./... clean — full repo, 42 packages, all ok
  • go test -race -count=1 ./relay/... run independently 4 times post-fix (no cache hits), all clean

Unchecked error returns (errcheck), deprecated/simplifiable
expressions (staticcheck), and dead code (unused), surfaced while
wiring golangci-lint into CI for the first time. Mostly mechanical
discards (hash.Hash.Write and http.ResponseWriter.Write never
meaningfully fail; resp.Body.Close/os.Remove in tests are best-effort
cleanup), plus two real test-correctness fixes: gift_wrap_test.go and
nipAZ_more_test.go both silently ignored a Sign()/comparison result
that could have masked a real failure.

Refs #6
Unchecked SetReadDeadline/SetWriteDeadline/Close/WriteControl/
WriteMessage return values, mechanically discarded — best-effort on a
possibly-already-closing connection, matching the existing pattern in
relay/session.go.

Refs #6
Unchecked error returns (mostly best-effort Close/Delete/View/Fetch
calls, mechanically discarded), deprecated/simplifiable expressions,
an ineffectual assignment, and a dead method, surfaced while wiring
golangci-lint into CI for the first time.

Two real fixes among the mechanical noise:
- handler_cache.go silently ignored a malformed cache-options JSON
  payload instead of rejecting it, unlike its sibling five lines above
  which already handled the same case correctly.
- store.go's event-delete path had an empty if-err-nil branch on a
  Put() failure (the error was checked, then discarded) instead of
  propagating it — the write could fail leaving the expiration index
  only partially updated, with nothing surfacing that. startHousekeeper
  now also logs pruneExpiredEvents' error instead of discarding it.

Refs #6
…tines in tests

go test -race ./relay/... intermittently failed with a genuine
WARNING: DATA RACE between a Session's read/write/pingLoop goroutines
(relay/session.go) from one test and a completely unrelated later
test's fresh allocations (e.g. NewEventStore's migration write) --
caught only now because -race was never part of CI before this PR.

Root cause: gorilla/websocket hijacks the underlying TCP connection on
upgrade. httptest.Server tracks connection lifecycle via ConnState and
treats a hijacked connection as already accounted for the instant it's
hijacked, so srv.Close()'s own WaitGroup does not wait for that
session's own goroutines to actually exit. t.Cleanup(srv.Close) alone
therefore never guaranteed a prior test's session had finished before
the next test started, letting its goroutines keep running -- and
occasionally race -- against whatever memory a later test's next
allocation happened to reuse.

Added waitForSessionCount, polling SessionHandler.SessionCount() down
to the expected value, and wired it into every place a test dials a
websocket and needs the server-side session to be fully torn down
before moving on: the two CORS tests and TestSession_GettersAndSessionCount
in cors_test.go, the shared createWS test helper (used package-wide),
and TestNew_StillUpgradesWebSocket in relay_test.go.

Verified with 4 independent, uncached (-count=1) full go test -race
./relay/... runs post-fix, plus one full go test -race ./... run
across every package -- all clean.

Refs #6
Clears 13 stdlib CVEs (crypto/tls, crypto/x509, net/http) that
govulncheck flags against 1.25.5 as reachable through relay/client and
nipB7/client -- not nmilat code bugs, just an outdated toolchain floor.

Refs #6
Brings ci.yml up to this org's own established pattern (ncli already
uses go-version-file and -race) plus general Go-library practice:

- go-version-file: go.mod instead of a hardcoded "1.25", so CI and
  go.mod can't silently drift apart.
- go test -race: nmilat has already shipped one real goroutine-leak
  bug (0.2.5) and this PR fixes a second one that -race caught for the
  first time -- this class of bug needs the gate, not just luck.
- New gates: gofmt -l (build fails the same job, not a separate one,
  so a failure is immediately visible alongside build/vet/test),
  go mod tidy diff check, golangci-lint (separate job, full repo,
  blocking -- the 155 pre-existing findings it surfaced are already
  fixed on this branch), govulncheck (separate job, blocking -- now
  clean after the 1.26.8 bump).
- Explicit permissions: contents: read (least privilege; release.yml
  already scoped its own permissions correctly, ci.yml hadn't).
- concurrency: cancel-in-progress, so superseded pushes don't queue.

Refs #6
@naliyi
naliyi merged commit d2818ad into main Sep 3, 2026
3 checks passed
naliyi added a commit that referenced this pull request Sep 3, 2026
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.

CI: adopt gofmt/lint/race/vulncheck gates, bump to go1.26.8

1 participant