skip to content

How does the GOFLAGS environment variable change what `go build` and `go test` do?

level: middleimportance: nice to knowfreq 26%

answer

  1. flags nobody typed
  2. a default source for the go command
  3. space-separated, standalone entries
  4. go env -w makes it persist
  5. the command line still wins

basics

~20 s

GOFLAGS holds a space-separated list of flags the go command applies by default to any subcommand that knows them, so GOFLAGS=-trimpath makes every build trimmed without editing a single command line. Flags given explicitly on the command line override it.

solid answer

~50 s

`GOFLAGS` is a space-separated list of standalone flag settings, each of the form `-flag` or `-flag=value`, that the `go` command applies by default. A flag is applied only to subcommands that know it, so `GOFLAGS=-trimpath` affects `go build`, `go test` and `go install` without breaking unrelated subcommands, and values may not contain spaces. Flags written on the command line are applied afterwards and therefore win. You can set it per-shell like any environment variable, or persist it with `go env -w GOFLAGS=-trimpath`, which writes to the `go` environment configuration file so every future command on that machine picks it up. That persistence is exactly why it deserves suspicion when a build misbehaves: the flag is invisible in the command that was typed, it changes the build cache key, and a laptop with something in `go env GOFLAGS` will not reproduce CI. `go env GOFLAGS` is the first thing to check.

code

text · 8 lines
text
$ go env -w GOFLAGS=-trimpath
$ go env GOFLAGS
-trimpath

# every build on this machine is now trimmed, even though nothing says so:
$ go build ./...

$ go env -u GOFLAGS    # remove the setting again

go deeper

for a junior

Know that GOFLAGS supplies default flags to go commands, that each entry is a standalone -flag or -flag=value, and that go env GOFLAGS shows what is set.

for a middle

Explain the precedence and scoping rules: entries apply only to subcommands that know the flag, the command line overrides them, and go env -w persists the value machine-wide.

for a senior

Treat it as an invisible build input: check it first when two machines disagree, and know that it shifts build cache keys and so quietly costs hit rate across a fleet.

for a principal

Decide what may live in a machine-wide default at all, and insist that anything load-bearing for a release is also written explicitly where the release runs so the log is self-describing.

## What it is `GOFLAGS` is an environment variable read by the `go` command itself. Its value is a **space-separated list of standalone flag settings**, each written as `-flag` or `-flag=value`. Before running a subcommand, the `go` command applies each entry that the subcommand recognises, then applies whatever was written on the command line — so an explicit flag overrides the same flag from `GOFLAGS`. Two consequences of the format matter in practice: - **Each entry must be standalone.** `-tags integration` as two words is wrong; `-tags=integration` is right, because the list is split on spaces. - **Values cannot contain spaces**, for the same reason. A flag whose value is a multi-word string cannot be expressed here. And one consequence of the "known to the current command" rule: a flag meaningful to `go build` does not break `go list` or `go env`, which simply ignore it. ## Why anyone reaches for it The honest use case is applying a build-shaping flag uniformly without touching every invocation. `-trimpath` is the canonical example: you want every artefact this machine produces to be path-free, and you do not want to rely on every script, every Makefile target and every developer remembering the flag. Setting it once means the tenth build script written next year is trimmed too. ``` $ go env -w GOFLAGS=-trimpath $ go env GOFLAGS -trimpath ``` `go env -w` writes the setting into the `go` command's own environment configuration file rather than into your shell profile, so it survives new shells and applies to every Go project on the machine. `go env -u GOFLAGS` removes it again. ## Why it is also a trap Everything good about `GOFLAGS` — invisible, persistent, machine-wide — is what makes it a debugging hazard. 1. **It does not appear in the command that was run.** A build log showing `go build ./...` tells you nothing about the flags actually in effect. When a colleague's build behaves differently from yours and the commands match, `go env GOFLAGS` on both machines is the first comparison to make. 2. **It changes build cache keys.** Build flags are part of the key, so a machine with `GOFLAGS=-trimpath` and a machine without it populate different cache entries for the same packages. In a shared or persisted cache this shows up as a mysteriously low hit rate rather than as an error. 3. **It is machine-wide.** `go env -w` is not scoped to a project. A flag set for one repository silently applies to every other repository on that machine, which is how a setting adopted for a good reason in one place becomes an unexplained difference in another. 4. **It survives the person who set it.** A laptop configured two years ago carries the setting into every new checkout; nothing in the repository records that it exists. ## Using it responsibly The practical rule is that `GOFLAGS` should carry only settings you would be happy to apply to *every* Go build on the machine, and that anything load-bearing for a release should also be written explicitly where the release is produced. Belt and braces: the flag is in `GOFLAGS` so nobody forgets it, and spelled out in the release command so the log proves it was used. Anything narrower — a flag that matters for one repository, one target, one job — belongs in that repository's build script, where a reader can see it. When a build produces something surprising and the command line looks innocent, the checklist is short: `go env GOFLAGS`, `go env GOOS GOARCH CGO_ENABLED`, and the toolchain version. The environment is part of the build inputs whether or not anyone wrote it down.

  • Where does `go env -w GOFLAGS=-trimpath` store the setting, and what is its scope?
    It writes into the `go` command's own environment configuration file, not your shell profile, so it survives new shells and reboots and applies to every Go project on that machine. `go env GOFLAGS` reads it back and `go env -u GOFLAGS` removes it. Nothing in any repository records that it is set.
  • What happens if the same flag appears in GOFLAGS and on the command line?
    The command line wins: entries from `GOFLAGS` are applied first and the explicit flag is applied after, overriding it. That makes a one-off override easy, but it also means a build log showing a flag tells you nothing about what else `GOFLAGS` contributed silently.
  • Why can GOFLAGS quietly reduce build cache hit rates across a team?
    Build flags are part of the build cache key. A machine with `GOFLAGS=-trimpath` and one without it compute different keys for the same packages, so they never share entries. In a persisted or shared cache the symptom is a low hit rate and slow builds, never an error message.

saying these in an interview costs you the question

  • Writes multi-word entries like -tags integration in GOFLAGS
  • Thinks GOFLAGS overrides flags given on the command line
  • Assumes go env -w only affects the current project
  • Never checks go env GOFLAGS when two machines build differently
  • Ignores that GOFLAGS silently changes build cache keys