Skip to content

release: stop running go mod tidy at release time, assert tidiness in CI - #642

Merged
zzet merged 1 commit into
mainfrom
fix/release-module-tidiness
Aug 20, 2026
Merged

release: stop running go mod tidy at release time, assert tidiness in CI#642
zzet merged 1 commit into
mainfrom
fix/release-module-tidiness

Conversation

@zzet

@zzet zzet commented Aug 20, 2026

Copy link
Copy Markdown
Owner

What broke

The v0.63.8 release failed in goreleaser's before hook:

• running before hooks
  • running                                        hook=go mod tidy
    • took: 4m49s
⨯ release failed after 4m53s   error=exit status 1 message=hook failed: command failed cmd=go

Six unrelated module fetches got HTTP/2 INTERNAL_ERROR from proxy.golang.orggomlx, four go-sitter-forest grammars, tree-sitter-protobuf. A transient proxy flake, but it took the release down after ~5 minutes, and nothing had been published yet.

Why the hook has to go, not just get a retry

go mod tidy resolves the test dependencies of the dependencies — that's why the failure log is full of ... tested by ... .test imports ... chains. It touches far more of the network than building the release needs, on the one path where a flake costs a release.

It is also unsound in a quieter way: it mutates go.mod/go.sum inside the release container. A drifted module set would have been silently rewritten at tag time and the published binaries built against something no CI job had ever compiled. A rewrite is not a check.

The config already skips tests in this hook on the reasoning that "the tag is already on a green commit". The same argument applies here.

Correction to what I said when I proposed this

I claimed CI already verified tidiness. It does not — there is no go mod tidy check in any workflow or in the Makefile. This release-time hook was the only thing exercising it, and it did so by rewriting rather than failing. So dropping it alone would have removed the last (bad) tidy pass.

This PR therefore does both halves:

  • removes the go mod tidy before-hook from .goreleaser.yml
  • adds a go.mod is tidy step to the lint job: go mod tidy then git diff --exit-code go.mod go.sum, which fails the PR that introduces drift

main is already tidy, so the new gate is green on landing.

Verification (real toolchain, not by reading it)

  • goreleaser build --snapshot with a deliberately failing before-hook aborts at running before hooks in 0s — proving hooks do execute in that mode.
  • With hooks: [], that stage never appears; the run goes straight from snapshotting to building binaries. No default hook silently takes its place.
  • goreleaser check validates the config.
  • The new CI gate passes on main unchanged, and exits 1 naming go.mod when run against a commit carrying a deliberately untidy require line.

Not included

Re-running the failed v0.63.8 release — that's yours to trigger. State is clean for it: no v0.63.8 GitHub release was created, and the tap is untouched.

…in CI

goreleaser ran `go mod tidy` as a before-hook inside the release container.
It took v0.63.8 down: the hook spent 4m49s walking the module graph and then
failed when proxy.golang.org returned HTTP/2 INTERNAL_ERROR on six unrelated
fetches. `go mod tidy` resolves the test dependencies of dependencies too, so
it touches far more of the network than a build needs, on the one path where
a flake costs a release.

The hook was also unsound in a quieter way. It MUTATES go.mod/go.sum, so a
drifted module set would have been silently rewritten inside the container
and the published binaries built against something no CI job had ever
compiled. A release-time rewrite is not a check.

Drop it — the config already skips tests here on the reasoning that the tag
is on a green commit, and the same argument applies.

Nothing verified module tidiness before this: no workflow, no Makefile
target. So the `lint` job gains a `go mod tidy` + `git diff --exit-code`
step, which fails the PR that introduces drift instead of papering over it
at tag time. main is already tidy, so the gate is green on landing.

Verified with the real toolchain, not by reading it:

  * `goreleaser build --snapshot` with a deliberately failing before-hook
    aborts at "running before hooks" in 0s, so hooks do execute in that mode;
    with `hooks: []` the stage never appears and the run goes straight from
    snapshotting to building. No default hook takes its place.
  * `goreleaser check` validates the config.
  * The CI gate passes on main unchanged, and fails with exit 1 (naming
    go.mod) against a commit carrying a deliberately untidy require line.
@zzet
zzet merged commit 50495e9 into main Aug 20, 2026
10 checks passed
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