skip to content

In go.mod, what does the `toolchain go1.27.0` directive do that the `go` line does not?

level: middleimportance: should knowfreq 38%

answer

  1. one line is a requirement, the other a preference
  2. ordering rule between the two values
  3. a setting outside the repo can ignore it
  4. go mod edit has a flag that deletes it
  5. the workspace file overrules the module

basics

~20 s

The go line states the minimum Go version a module requires. The toolchain line names which release to switch to when a switch happens, must not be lower than the go line, and is ignored when switching is disabled.

solid answer

~50 s

Two different jobs. The `go` line is the module's hard minimum — any toolchain older than it must not build the module. The `toolchain` line is a *preference*: when the go command decides it has to switch, this is the release it switches to, so a repository can say "build me with 1.27.0" without forcing every consumer to require 1.27 as a minimum. The toolchain version must be at least the go version. It is written for you when you move the go line ahead of your installed Go, and you can manage it directly with `go mod edit -toolchain=go1.27.0` or delete it with `go mod edit -toolchain=none`. Crucially it is inert under `GOTOOLCHAIN=local`, because it only ever describes which switch to make, and there you have forbidden switching. In a workspace, `go.work`'s own lines take precedence over the module's.

code

text · 2 lines
text
$ go mod edit -toolchain=go1.27.0
$ go mod edit -toolchain=none

go deeper

for a junior

Recognise the toolchain line when you see it in go.mod, and know it names which Go release a build should switch to rather than the minimum the module requires.

for a middle

Explain the ordering rule against the go line, how the directive is written and removed with go mod edit, and that it is only consulted when the environment permits a toolchain switch.

for a senior

Show judgment about which line to move for which goal, spot an accidental toolchain bump in a go.mod diff, and know that a pinned build image overrides the repository's preference.

for a principal

Decide as a policy who is allowed to move each line, and whether repositories may express a toolchain preference at all when the fleet pins its compilers centrally.

## Two lines, two jobs A modern `go.mod` can carry both: ``` go 1.24.0 toolchain go1.27.0 ``` They are not redundant. The `go` line is a *requirement* on anyone who builds this module: a toolchain older than 1.24.0 must refuse. The `toolchain` line is a *suggestion about which toolchain to run*: if the go command in front of the module is going to switch, this names the destination. The distinction is what lets a library keep a low minimum while its own developers and CI use something recent. Raising the `go` line to 1.27 would force every downstream consumer onto Go 1.27; adding `toolchain go1.27.0` only affects builds of this module in an environment where switching is permitted. ## The rules - The toolchain version must not be lower than the go version. A go.mod claiming `go 1.27.0` with `toolchain go1.24.0` is rejected, because the named toolchain could not satisfy the module's own minimum. - The directive is optional. With no toolchain line, the go command that needs to switch selects a release satisfying the go line. - The value `toolchain default` is also allowed and means "use the go command's own bundled toolchain", i.e. do not record a preference for anything newer. - In a workspace, `go.work` may carry its own `go` and `toolchain` lines, and those govern commands run in the workspace, overriding what the individual modules say. ## How it gets there Most people never type it. Moving the module's required version forward past what you have installed — for example with `go get [email protected]` — will record a toolchain line so that the go command has something concrete to switch to. To manage it explicitly: - `go mod edit -toolchain=go1.27.0` sets it. - `go mod edit -toolchain=none` removes it. - `go mod edit -go=1.24.0` moves the requirement, which may cause the toolchain line to be adjusted so the ordering rule holds. Because it lives in `go.mod`, it is reviewable and versioned: a toolchain bump is a diff somebody approves, not a change to a machine's configuration that nobody can see. ## What it cannot do The most common misreading is that the toolchain line pins the compiler for everyone. It does not. It expresses which release to switch *to*, and switching is controlled by `GOTOOLCHAIN`: - Under `auto`, the line does what you expect: builders end up on the named release even if they installed something else. - Under `local`, the line is ignored entirely. The installed toolchain runs. The `go` line is still enforced as a minimum, so a machine below it errors, but nothing switches to the named release. - Under an explicit `GOTOOLCHAIN=go1.x` pin, that pin wins; the module's preference does not override the operator's. So the toolchain line is a repository-level *request* that an environment-level setting may decline. If your intent is "nobody may build this with an older compiler", the mechanism for that is the `go` line, not the toolchain line. If your intent is "our builds should use this release", the toolchain line plus `GOTOOLCHAIN=auto` gets you there, and a pinned build image gets you there more firmly. ## Why the split exists Before Go 1.21 there was only the go line, and it had to carry both meanings — which meant the only way to make CI use a newer compiler was to raise the minimum for every consumer of the library. Splitting the two lets the ecosystem's compatibility floor move slowly while individual repositories track releases quickly, and it makes the answer to "which compiler built this?" something recorded in the repository rather than folklore about the build agent. ## Reviewing one When you see a toolchain line change in a pull request, the questions worth asking are: does every environment that builds this repository have that release available, or will it be downloaded at build time; is the go line still where the library's consumers need it; and is the toolchain bump intentional, or a side effect of somebody running a command that rewrote go.mod on a newer laptop. That last case is the usual source of surprise toolchain lines.

  • Can the toolchain line be older than the go line?
    No. The named toolchain would be unable to satisfy the module's own minimum, so that combination is rejected. Tools that rewrite go.mod maintain the ordering for you: moving the go line up past the toolchain value adjusts the toolchain line rather than leaving an inconsistent pair behind.
  • A library author wants CI on the newest Go without forcing consumers to upgrade. What do they change?
    Add or bump the `toolchain` line and leave the `go` line alone. The go line is the compatibility promise made to consumers — raising it excludes anyone on an older release. The toolchain line only influences which release builds this module in environments that permit switching, so it is the right knob for the author's own builds.
  • Why might a toolchain line appear in a diff nobody intended to write?
    Commands that update the module's requirements rewrite go.mod, and if they run on a machine whose Go is newer than the module asks for, a toolchain line can be recorded as part of that rewrite. It shows up as an unrelated-looking change in a pull request. Reviewing go.mod diffs, and removing an unwanted line with `go mod edit -toolchain=none`, is the fix.

saying these in an interview costs you the question

  • Says the toolchain line pins the compiler for everyone
  • Treats the go line and toolchain line as interchangeable
  • Thinks the toolchain line still applies under GOTOOLCHAIN=local
  • Raises the go line when only CI needs the newer release
  • Believes go.mod always wins over go.work in a workspace