Skip to content

make modernize will break on the next Go pkgbits bump #7818

Description

@friedrichg

Describe the bug

make modernize has the same latent breakage that go install faillint had, and it will fail CI at the next Go release that increments the export data version.

Makefile:226:

modernize:
	GOTOOLCHAIN=auto go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.22.0 -fix ./...

go run pkg@version resolves the target's go.mod, not ours. So the x/tools that modernize is built against is whatever gopls v0.22.0 pins, regardless of what our module graph requires. It is pinned, not maintained by us, and coupled to the Go toolchain version with nothing recording that coupling.

The mechanism is the one described in #7815: the Go compiler writes type information at a pkgbits version, and an analysis tool whose x/tools is older than that version cannot decode it. Go 1.24 through 1.26 all wrote V2; Go 1.27 writes V4. A tool pinned to an x/tools that predates V4 fails with internal error: package "..." without types was imported from "...".

Why it is not broken right now

gopls v0.22.0 requires golang.org/x/tools v0.44.1-0.20260513175300-635ae9663724 (verified from gopls/go.mod at tag gopls/v0.22.0). That pseudo-version is dated 2026-05-13, which postdates both V4 support (x/tools v0.44.0) and the corrected unified reader (CL 765504, released in v0.45.0). So it decodes Go 1.27 output correctly.

That is luck, not design. The pin happens to be recent enough. Nothing ties it to the FROM golang: version in build-image/Dockerfile.

To Reproduce

Not reproducible today, which is the point. It becomes reproducible on the first Go release that writes pkgbits V5 or later:

  1. Bump build-image/Dockerfile to that Go version.
  2. Run make modernize (or make check-modernize).
  3. It fails with internal error: package "..." without types was imported from "...".

The same shape is reproducible now on Go 1.27 with a pre-V4 x/tools, using faillint as the stand-in tool, in golang:1.27.0-trixie:

$ go install github.com/fatih/faillint@v1.15.0     # pins x/tools v0.30.0, max V2
$ faillint -paths "sync/atomic=go.uber.org/atomic" ./...
faillint: internal error: package "net/http" without types was imported from "chk"
$ echo $?
1

Expected behavior

A Go toolchain bump should not be able to silently break a lint tool, and if it does, the failure should point at the thing that needs changing.

Impact

check-modernize runs in CI (.github/workflows/test-build-deploy.yml:42), so this fails the lint job. As with faillint, the failure will not surface in the PR that causes it: build-image/** is excluded from that workflow via paths-ignore, so it lands on whichever unrelated PR next bumps LATEST_BUILD_IMAGE_TAG.

Environment

Build and CI tooling, not a runtime issue. Affects make modernize, make check-modernize and the lint CI job.

Additional Context

Candidate fixes, in rough order of preference. None are verified yet:

  1. Declare modernize as a tool dependency, so MVS picks x/tools from our own module graph, as proposed for faillint in Build faillint from the main module instead of go install #7816. Open question: the command lives under gopls/internal/..., and it is not clear whether go get -tool accepts a path inside an internal subtree. That needs checking before assuming this works.
  2. Bump the pin and document the coupling, noting in the Makefile that the pin must move whenever the FROM golang: line in build-image/Dockerfile moves. Cheapest, but relies on someone reading a comment across a gap of potentially years.
  3. Drop the version and use @latest, which removes the coupling at the cost of reproducible builds. Inconsistent with how every other tool in the repo is pinned.

The two cases differ in one respect worth noting: gopls is actively maintained, so a newer pin is always available. faillint has had no commit since 2025-03, which is why the module-graph approach mattered more there.

I only searched the Makefile for other instances of this pattern. Any go run or go install of an x/tools-based analysis tool at a pinned version has it; faillint and modernize are the only two occurrences I found.


AI usage disclosure, per GENAI_POLICY.md: this issue was drafted with AI assistance (Claude Code). The factual claims were verified before filing: Makefile:226 and test-build-deploy.yml:42 read from the tree; the gopls v0.22.0 x/tools requirement read from gopls/go.mod at tag gopls/v0.22.0; the failure output reproduced in golang:1.27.0-trixie. The candidate fixes are explicitly marked unverified.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ci/cdtype/choreSomething that needs to be done; not a bug or a feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions