ci: run CI's linter locally, and stop timing out on slow Windows runners

Two things cost a full CI round trip each, both avoidable.

REASONIX.md told contributors golangci-lint was "not locally available", so
nobody ran it. It always was: lint-cross already shells out to it. What was
missing is a pinned version and one command. `.golangci-version` is now the
single pin the Makefile and the lint job both read, `make lint-install`
installs it (with CGO_ENABLED=0, so a stray clang on PATH cannot break the
install), and `make lint` runs the same two gates CI runs, in the same order.
Replaying a recent PR's diff through it reproduces the exact `modernize`
finding CI took ten minutes to report.

The Windows smoke step then failed on a run whose only change was two test
fixtures. Across four consecutive runs of near-identical code, internal/boot
measured 133s, 146s, 162s and 180s, and internal/agent 82s, 92s, 144s and
180s: a 2.2x spread from runner variance, four heavy packages sharing four
vCPUs. A 3 minute per-package timeout sat inside that spread, so the step was
one slow runner away from failing at any time. It is 8m now, still well inside
the step's own 15 minute budget, which is what actually catches a hang.

Neither change touches what is tested.
This commit is contained in:
esengine committed 2026-08-09 02:11:36 -07:00
1 parent c03291cbd1
commit 8acb73eb07
4 files changed
+39 -7

No files matched your search

+15 -3
View File
@@ -138,14 +138,21 @@ jobs:
# filesystem contracts, plus cmd/. The full ./... sweep stays on pushes
# to main-v2; the Unix legs always run the full suite. Keep the job-level
# budget above normal 7-9 minute runner variance; each package test binary
# remains independently bounded by Go's 3 minute timeout below.
# remains independently bounded by Go's per-package timeout below.
#
# That timeout is 8m, not 3m: across four consecutive runs of the same
# code, internal/boot measured 133s, 146s, 162s and then hit 180s, and
# internal/agent 82s, 92s, 144s and 180s — a 2.2x spread from runner
# variance alone, with four heavy packages sharing four vCPUs. At 3m the
# step failed on slow runners without a code change; a hang still trips
# the 15 minute job budget above.
- name: test (Windows smoke)
if: env.RUN_STEPS == 'true' && runner.os == 'Windows' && github.event_name == 'pull_request'
timeout-minutes: 15
env:
REASONIX_RELEASE_CACHE_GUARD: "1"
WINDOWS_SANDBOX_WAIT_MS: "20000"
run: go test -p 4 -timeout=3m ./internal/agent/... ./internal/appidentity/... ./internal/boot/... ./internal/checkpoint/... ./internal/cli/... ./internal/control/... ./internal/desktoplauncher/... ./internal/filelock/... ./internal/fileutil/... ./internal/hook/... ./internal/instruction/... ./internal/mcplaunch/... ./internal/notify/... ./internal/proc/... ./internal/remote/... ./internal/repair/... ./internal/sandbox/... ./internal/sysproxy/... ./internal/workspacelease/... ./cmd/...
run: go test -p 4 -timeout=8m ./internal/agent/... ./internal/appidentity/... ./internal/boot/... ./internal/checkpoint/... ./internal/cli/... ./internal/control/... ./internal/desktoplauncher/... ./internal/filelock/... ./internal/fileutil/... ./internal/hook/... ./internal/instruction/... ./internal/mcplaunch/... ./internal/notify/... ./internal/proc/... ./internal/remote/... ./internal/repair/... ./internal/sandbox/... ./internal/sysproxy/... ./internal/workspacelease/... ./cmd/...
# The general Windows PR smoke list intentionally omits internal/tool.
# Keep the session-temp portability contract covered without widening the
@@ -485,10 +492,15 @@ jobs:
- name: repo standards
run: go run ./tools/repolint
# `make lint-install` reads the same file, so a local run and this job
# cannot drift onto different linter versions.
- id: golangci
run: echo "version=$(cat .golangci-version)" >> "$GITHUB_OUTPUT"
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12.2
version: ${{ steps.golangci.outputs.version }}
args: --timeout=5m
# The step above only type-checks the linux/amd64 build, so every
+1
View File
@@ -0,0 +1 @@
v2.12.2
+18 -2
View File
@@ -6,8 +6,10 @@ LDFLAGS := -s -w \
-X main.gitCommit=$(GIT_COMMIT) \
-X main.buildTimeUTC=$(BUILD_TIME_UTC)
GOEXE := $(shell go env GOEXE)
# One pin for the Makefile and the CI lint job; see .github/workflows/ci.yml.
GOLANGCI_VERSION := $(shell cat .golangci-version)
.PHONY: build vet fmt lint lint-cross lint-update test desktop-test desktop-test-short desktop-test-times sdk-test sdk-test-race hooks cross clean
.PHONY: build vet fmt lint lint-go lint-install lint-cross lint-update test desktop-test desktop-test-short desktop-test-times sdk-test sdk-test-race hooks cross clean
build:
CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o bin/reasonix$(GOEXE) ./cmd/reasonix
@@ -19,9 +21,23 @@ vet:
fmt:
gofmt -w .
lint:
# Both gates CI runs, at the version CI pins. Skipping golangci-lint locally
# trades a second here for a ten-minute CI round trip: `modernize` findings in
# particular never surface in `go vet`.
lint: lint-go
go run ./tools/repolint
lint-go:
@command -v golangci-lint >/dev/null || { echo "golangci-lint not installed; run: make lint-install"; exit 1; }
@have=$$(golangci-lint version --short 2>/dev/null); want=$$(echo "$(GOLANGCI_VERSION)" | sed 's/^v//'); \
[ "$$have" = "$$want" ] || echo "warning: local golangci-lint $$have, CI pins $$want (make lint-install)"
golangci-lint run --timeout=5m ./...
# CGO_ENABLED=0 keeps the install working where a stray clang on PATH shadows
# the toolchain and breaks runtime/cgo.
lint-install:
CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_VERSION)
lint-update:
go run ./tools/repolint -update
+5 -2
View File
@@ -66,11 +66,14 @@ Run these **before every commit** to catch the fastest CI failures locally:
```bash
gofmt -w . # catches gofmt (saves ~13s CI)
go vet ./... # catches vet warnings (saves ~52s CI/lint)
go run ./tools/repolint # catches comment/size/layering regressions
make lint # golangci-lint at CI's pin + repolint
go test ./internal/tool/builtin/ ./internal/boot/ # catches tool/boot test breaks
```
CI runs `golangci-lint` (not locally available), but gofmt + vet already block ~80% of fast-fail scenarios.
`make lint` runs both gates CI runs, at the version in `.golangci-version`;
`make lint-install` installs it. Do not skip it: a `modernize` finding never
shows up in `go vet`, and the CI round trip that catches it instead costs ten
minutes.
## Import cycle rule