golang/go · error

module listed in go.work file requires go >= , but go.work…

Error message

module %s listed in go.work file requires go >= %s, but go.work %s go %s; to download and use go %s:
	go work use

What it means

errWorkTooOld: a module listed in go.work requires a newer Go version than go.work itself declares. Produced when a member's go.mod go directive exceeds the go.work go version (and the member is >= GoStrictVersion). The message tells the user to run 'go work use' which will bump go.work's go directive after they obtain the newer toolchain.

Solutions

  1. Upgrade the toolchain to the required version (GOTOOLCHAIN=auto or go get toolchain@go1.X).
  2. Run 'go work use' as the message instructs; it bumps the go.work go directive to match.
  3. Manually edit go.work 'go X.Y' to >= the member's version.
  4. If the member's high directive is unintended, lower that member's go directive instead.

Example fix

// before
go.work:  go 1.21
svc-b/go.mod:  go 1.22
// module svc-b listed in go.work requires go >= 1.22, but go.work lists go 1.21

// after
$ GOTOOLCHAIN=auto go work use   // go.work bumped to 'go 1.22'
Defensive patterns

Strategy: validation

Validate before calling

// Ensure go.work go version >= every member's go directive.
data, _ := os.ReadFile("go.work")
wf, _ := modfile.ParseWork("go.work", data, nil)
wv := gover.FromGoWork(wf)
for _, u := range wf.Use {
    mdata, _ := os.ReadFile(filepath.Join(u.Path, "go.mod"))
    mf, _ := modfile.Parse("go.mod", mdata, nil)
    mv := gover.FromGoMod(mf)
    if gover.Compare(mv, wv) > 0 {
        return fmt.Errorf("bump go.work go to %s (member %s needs it)", mv, u.Path)
    }
}

Try / catch

out, err := exec.Command("go", "build", "./...").CombinedOutput()
if err != nil && bytes.Contains(out, []byte("requires go >=")) && bytes.Contains(out, "go.work") {
    // bump toolchain then 'go work use' to realign
    _ = os.Setenv("GOTOOLCHAIN", "auto")
    _ = exec.Command("go", "work", "use").Run()
    out, err = exec.Command("go", "build", "./...").CombinedOutput()
}
return err

Prevention

When it happens

Trigger: go.work says 'go 1.21' but a member go.mod says 'go 1.22'; both are >= GoStrictVersion so the comparison gover.Compare(mv, wv) > 0 triggers errWorkTooOld. Also fires via the TooNewError branch in 1069's neighborhood.

Common situations: Adding/upgrading a dependency submodule that bumped its go directive; merging a newer member into an older workspace; CI toolchain behind the member requirements.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/6530871c4f873c7c. Report an issue: GitHub.

Appendix: source

Thrown at src/cmd/go/internal/modload/init.go:1167

				}
			}
		} else {
			rawGoVersion.Store(mainModule, gover.DefaultGoModVersion)
		}
	}

	ld.requirements = rs
	return ld.requirements, nil
}

func errWorkTooOld(gomod string, wf *modfile.WorkFile, goVers string) error {
	verb := "lists"
	if wf == nil || wf.Go == nil {
		// A go.work file implicitly requires go1.18
		// even when it doesn't list any version.
		verb = "implicitly requires"
	}
	return fmt.Errorf("module %s listed in go.work file requires go >= %s, but go.work %s go %s; to download and use go %s:\n\tgo work use",
		base.ShortPath(filepath.Dir(gomod)), goVers, verb, gover.FromGoWork(wf), goVers)
}

// CheckReservedModulePath checks whether the module path is a reserved module path
// that can't be used for a user's module.
func CheckReservedModulePath(path string) error {
	if gover.IsToolchain(path) {
		return errors.New("module path is reserved")
	}

	return nil
}

// CreateModFile initializes a new module by creating a go.mod file.
//
// If modPath is empty, CreateModFile will attempt to infer the path from the
// directory location within GOPATH.
//

View on GitHub (pinned to b6b368adc5)