skip to content

In go.mod, what does a require line like example.com/lib v3.2.0+incompatible mean?

level: middleimportance: should knowfreq 42%

answer

  1. the tag predates the module system
  2. look for a go.mod in that tree
  3. SemVer build metadata, not a pre-release
  4. no suffix demanded of a non-module
  5. it also declares no requirements of its own

basics

~20 s

The +incompatible marker means the dependency is tagged at major version 2 or higher but has no go.mod file at that tag. Never being modules-aware, it is allowed to keep the bare path with no /v2 style suffix.

solid answer

~40 s

Semantic import versioning demands a `/vN` suffix from v2 on, but that rule can only bind a repository that opted into modules. When the go command resolves a v2-or-higher tag on a module path with no suffix, it looks for a `go.mod` in that tagged tree. If there is none, the repository predates modules, so the go command accepts the version at the bare path and records it with the `+incompatible` build-metadata marker — `example.com/lib v3.2.0+incompatible`. If a `go.mod` is present, the version is rejected and you are told the path must end in `/v3`. Practically, `+incompatible` is a legacy signal: that dependency also declares no requirements of its own, so its transitive needs get resolved from its imports and land in your `go.mod` as `// indirect` lines.

code

mod · 8 lines
mod
module example.com/app

go 1.27

require (
	example.com/lib/v2 v2.3.0
	example.com/legacy v3.2.0+incompatible
)

go deeper

for a junior

Recognise the marker and know it is normal, not an error. It means the dependency is tagged past v1 but was never packaged as a Go module, so it keeps its unsuffixed path.

for a middle

Explain the check that produces it: the go command looks for a go.mod in the tagged tree, accepts the version with +incompatible when there is none, and rejects it when there is one. Note that it is SemVer build metadata.

for a senior

Discuss the operational consequence — such a dependency declares no requirements of its own, so its needs surface as indirect lines in your go.mod, and its majors cannot coexist, which can make two of your dependencies unsatisfiable together.

for a principal

Treat it as a supply signal about a dependency you may be committing your organisation to. Decide whether to live with it, push the maintainer toward a suffixed release, or budget an exit before the coexistence problem bites.

## Where the marker comes from Go modules arrived long after Go code did. Plenty of repositories had already been tagged v2.0.0, v3.1.4 and beyond under older tooling that had no concept of a module path, let alone a major version suffix. Enforcing semantic import versioning retroactively would have made all of those versions unreachable. `+incompatible` is the escape hatch that keeps them reachable. ## The exact rule When the go command is asked for a version with major 2 or higher at a module path that has **no** major version suffix, it fetches that tag and looks for a `go.mod` file at the root of the tree: - **No `go.mod` in the tagged tree** — the repository never adopted modules, so it cannot have been expected to declare a suffix. The version is accepted at the bare path and written with the `+incompatible` marker: `require example.com/lib v3.2.0+incompatible`. - **A `go.mod` is present** — the repository is modules-aware and simply got the rule wrong. The version is rejected, and the go command reports that the module path must end in `/v3`. `+incompatible` is SemVer *build metadata*, which is why it can be attached without inventing new syntax. Within that one module path, the go command orders these versions by their numbers as usual, so v3.2.0+incompatible sits above v1.9.0. ## What the marker actually tells you about the dependency The name is a good one: the version is genuinely *incompatible* with the module system's guarantees, and two consequences follow. **It declares no requirements.** A module with no `go.mod` says nothing about what it needs. The go command has to derive that from the packages it imports, and those derived requirements end up recorded in *your* main module's `go.mod` as `// indirect` lines. You are carrying dependency information the library should have carried itself. **Its majors are not separated.** The whole point of the `/vN` suffix is that v1 and v3 are different modules that can coexist. A `+incompatible` module has one path for all of its majors, so the build list holds exactly one of them. If one dependency of yours needs its v1 API and another needs its v3 API, there is no arrangement that satisfies both — you are back in classic diamond territory, which is precisely what semantic import versioning exists to prevent. ## What happens when the library modernises Suppose the maintainer finally adds a `go.mod` and releases v4 correctly, as `example.com/lib/v4`. From the go command's point of view a brand new module has appeared. The old `+incompatible` versions do not migrate to it and are not superseded by it; they remain available at the bare path for anyone still requiring them. Your move to v4 is the ordinary major migration: require the `/v4` path and rewrite the imports. Nothing automatic connects the two. ## What to do when you see one A `+incompatible` line in a `go.mod` you own is not an error and does not need to be fixed today, but it is worth reading as a maturity signal about that dependency. Three reasonable responses: 1. **Leave it** if the library is stable and lightly used. It builds reproducibly like anything else; `go.sum` still pins its content. 2. **Ask for a modules-aware release** if you are a significant consumer. The fix on the library's side is small: add a `go.mod` with the suffixed path and cut a fresh major. 3. **Plan an exit** if you are hitting the coexistence problem, where two of your dependencies want different majors of it. That constraint is unfixable from your side; only a suffixed release, a fork, or dropping one of the two dependencies resolves it. ## The thing candidates get wrong The common wrong reading is that `+incompatible` describes an incompatibility with *your* Go version, or a failed checksum, or a pre-release. It is none of those. It is a statement about the dependency's own packaging: tagged past v1, never modules-aware, therefore exempted from the suffix rule it was never in a position to follow.

  • What decides between +incompatible and an outright rejection of the tag?
    The presence of a `go.mod` file in the tagged tree. Without one, the repository never adopted modules and the version is accepted at the bare path with the `+incompatible` marker. With one, the repository is modules-aware, so the suffix rule applies and the go command refuses the version, saying the module path must end in `/vN`.
  • Why do you often see extra // indirect lines when depending on a +incompatible module?
    Because that module carries no `go.mod`, it declares no requirements. The go command works out what its packages import and records those modules in your own `go.mod` as indirect requirements, so your file ends up holding dependency information the library should have declared itself.
  • If that library later publishes a correct example.com/lib/v4, what happens to the +incompatible versions?
    Nothing automatic. `/v4` is a different module path, so the old versions stay published and resolvable at the bare path, and no upgrade command crosses over. Moving is the ordinary major migration: require the suffixed path and rewrite imports.

saying these in an interview costs you the question

  • Reads it as incompatible with your Go version
  • Thinks it signals a checksum or go.sum mismatch
  • Calls it a pre-release or unstable tag
  • Believes the go command adds it to any v2+ tag
  • Says it can be fixed by editing your own go.mod