From 1dd26866c22e9e17f62981b81a40867a346e424e Mon Sep 17 00:00:00 2001 From: Blair Hamilton Date: Sun, 5 Jul 2026 10:47:26 -0400 Subject: [PATCH] fix(golang): respect a caller-set GOTOOLCHAIN when installing hook envs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InstallEnvironment unconditionally appended GOTOOLCHAIN=local, clobbering any toolchain the caller pinned. In CI (pinpredict pre-commit-advisory) the workflow pins GOTOOLCHAIN=go precisely so hooks whose module requires a newer Go than the runner's PATH go can build — e.g. golangci-lint v2.12.2 needs go >= 1.25.0 while ubuntu-latest ships 1.24.13, so every golang hook install failed with 'go.mod requires go >= 1.25.0 (running go 1.24.13; GOTOOLCHAIN=local)'. Default to local only when GOTOOLCHAIN is unset; an explicit caller value now passes through. Co-Authored-By: Claude Fable 5 --- internal/languages/golang.go | 22 +++++++++++++++++----- internal/languages/golang_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 internal/languages/golang_test.go diff --git a/internal/languages/golang.go b/internal/languages/golang.go index 85024ab..29e426c 100644 --- a/internal/languages/golang.go +++ b/internal/languages/golang.go @@ -25,15 +25,27 @@ func (g *Golang) HealthCheck(prefix, version string) error { return nil } -func (g *Golang) InstallEnvironment(prefix, version string, additionalDeps []string) error { - envDir := filepath.Join(prefix, g.EnvironmentDir()+"-"+version) - +// goInstallEnv builds the env overrides for installing a golang hook env. +// GOTOOLCHAIN defaults to "local" so a hook repo's go.mod can't pull in a +// different toolchain — unless the caller set GOTOOLCHAIN explicitly. CI pins +// a repo-matching toolchain (e.g. GOTOOLCHAIN=go1.26.4) so hooks whose module +// requires a newer Go than the one on PATH can still build; forcing "local" +// over that pin makes such installs unbuildable. +func goInstallEnv(envDir string) []string { env := []string{ fmt.Sprintf("GOPATH=%s", envDir), fmt.Sprintf("GOBIN=%s", filepath.Join(envDir, "bin")), - // Don't let a hook repo's go.mod pull in a different toolchain. - "GOTOOLCHAIN=local", } + if os.Getenv("GOTOOLCHAIN") == "" { + env = append(env, "GOTOOLCHAIN=local") + } + return env +} + +func (g *Golang) InstallEnvironment(prefix, version string, additionalDeps []string) error { + envDir := filepath.Join(prefix, g.EnvironmentDir()+"-"+version) + + env := goInstallEnv(envDir) // Install the hook package. args := []string{"install", "./..."} diff --git a/internal/languages/golang_test.go b/internal/languages/golang_test.go new file mode 100644 index 0000000..b24ddd6 --- /dev/null +++ b/internal/languages/golang_test.go @@ -0,0 +1,30 @@ +package languages + +import ( + "slices" + "testing" +) + +// --------------------------------------------------------------------------- +// Golang — install env composition +// --------------------------------------------------------------------------- + +func TestGoInstallEnvDefaultsToLocalToolchain(t *testing.T) { + t.Setenv("GOTOOLCHAIN", "") + + env := goInstallEnv("/prefix/go_env-default") + if !slices.Contains(env, "GOTOOLCHAIN=local") { + t.Errorf("env %v should pin GOTOOLCHAIN=local when the caller sets nothing", env) + } +} + +func TestGoInstallEnvRespectsCallerToolchain(t *testing.T) { + // CI pins a repo-matching toolchain so hooks whose module requires a + // newer Go than PATH's can still build; the install must not override it. + t.Setenv("GOTOOLCHAIN", "go1.26.4") + + env := goInstallEnv("/prefix/go_env-default") + if slices.Contains(env, "GOTOOLCHAIN=local") { + t.Errorf("env %v must not force GOTOOLCHAIN=local over the caller's pin", env) + } +}