Skip to content

build: pin the gate's Go toolchain to the one go.mod names - #449

Merged
fuad-daoud merged 1 commit into
mainfrom
build/pin-the-gate-toolchain
Sep 10, 2026
Merged

build: pin the gate's Go toolchain to the one go.mod names#449
fuad-daoud merged 1 commit into
mainfrom
build/pin-the-gate-toolchain

Conversation

@fuad-daoud

Copy link
Copy Markdown
Collaborator

Closes #431.

Summary

The Makefile says make gate is what CI runs, and that property is why a
red gate is worth believing. It was not true on a machine with a newer Go than
CI’s.

golangci-lint’s bundled staticcheck builds its own IR of every package it loads,
the standard library included, so a stdlib newer than that release knows panics
it before it examines a line of this repo. On Go 1.27 the gate failed at lint
with five panics in internal/poll (unexpected expr: *ast.KeyValueExpr,
cascading into fact_purity, nilness, typedness and SA5012), and
coverage then failed two rows that pin an encoding/json escape 1.27 spells
differently. Neither has anything to do with the change under test, which is the
situation that teaches people to ignore a red gate.

The gate now pins GOTOOLCHAIN, read from go.mod’s own go directive so the
version has one definition rather than a copy, and exported so the scripts and
golangci-lint see it too — the linter reads the stdlib through go list. CI
reads the same line through setup-go’s go-version-file, replacing the literal
go-version: "1.26.3" that was a second copy of it. make errors if that
directive cannot be read, rather than silently exporting a meaningless pin.

A GOTOOLCHAIN already set in the environment still wins, as an override should,
and is reported — exactly how a local golangci-lint of the wrong version is
already handled. Reporting rather than refusing is the existing convention here:
a toolchain setting is not a reason to decline to run the gate at all.

What this does not do

It does not move the toolchain. Bumping it needs three things to move together,
or the gate fails on something other than the change under test:

  1. the go directive in go.mod,
  2. GOLANGCI_LINT_VERSION, to a release whose staticcheck knows the newer stdlib,
  3. the two rawDivergences rows pinning an encoding/json escape.

Both ends now carry a comment saying so, at the Makefile pin and at the
divergence rows, so whoever attempts the bump reaches the constraint from either
direction. Those rows claim the old conversion lost a byte, not that a version
of encoding/json spelled the loss one way — so they are over-specific relative
to their own stated purpose, and rewriting them belongs to the bump rather than
to this change.

Test plan

Measured on a Go 1.27 machine with nothing set in the environment:

  • pristine main: make gateexit 2, failing at lint with 5 staticcheck
    panics.
  • with this change: make gateexit 0, no env var, full suite under -race
    at 100% coverage.

Also checked: make -s print-go-version reports 1.26.3 from go.mod; an
overriding GOTOOLCHAIN emits the mismatch warning and still runs; the workflow
parses and its setup-go step resolves to go-version-file: go.mod.

🤖 Generated with Claude Code

https://claude.ai/code/session_016EHKV7ZYQJJXCPyynTWq4P

The Makefile's claim is that `make gate` is what CI runs, and that is what makes
a red gate worth believing. It was not true on a machine with a newer Go than
CI's: golangci-lint's bundled staticcheck builds its own IR of every package it
loads, the standard library included, so a stdlib it does not know panics it
before it reaches a line of this repo. On Go 1.27, `make gate` failed at `lint`
with five panics in `internal/poll`, and `coverage` then failed two rows that
pin an encoding/json escape 1.27 spells differently — neither having anything to
do with the change under test, which is the situation that teaches people to
ignore a red gate.

The gate now pins GOTOOLCHAIN, read from go.mod's own go directive so the
version has one definition, and exported so the scripts and golangci-lint see
it too — the linter reads the stdlib through `go list`. CI reads the same line
through setup-go's go-version-file, replacing the literal that was a second
copy of it. A GOTOOLCHAIN already set in the environment still wins and is
reported, exactly as a local golangci-lint of the wrong version is.

Measured on a Go 1.27 machine with nothing set: `make gate` exits 2 before this
change and 0 after.

This does not move the toolchain. Bumping it needs a golangci-lint whose
staticcheck knows the newer stdlib and a rewrite of the two rawDivergences rows,
which are now commented where each will be reached; that is a deliberate change
of its own, not a side effect of this one.

Closes #431

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016EHKV7ZYQJJXCPyynTWq4P
@fuad-daoud
fuad-daoud merged commit be35616 into main Sep 10, 2026
1 check passed
@fuad-daoud
fuad-daoud deleted the build/pin-the-gate-toolchain branch September 10, 2026 10:44
@fuad-daoud
fuad-daoud restored the build/pin-the-gate-toolchain branch September 10, 2026 18:17
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.

make gate does not pass under Go 1.27: linter panic plus one pinned test

2 participants