Modules, MVS, and Toolchain

Updated

September 8, 2026

Modules, MVS, and Toolchain

Overview

Go modules are a versioned dependency graph with a precise selection algorithm: Minimal Version Selection (MVS). Treat go.mod / go.sum as production config — not editor noise.

Basics: Dependency management, Workspaces.

Diagram: MVS selection

flow:
  [Max]
       |
       v
  [Build]

Core Files

File Role
go.mod Module path, Go version, requires, replaces, excludes, toolchain
go.sum Cryptographic checksums of module contents
vendor/ Optional frozen source trees
go mod tidy      # align requires with imports
go mod download
go mod verify
go mod graph

Minimal Version Selection

Unlike many package managers that pull “newest compatible,” MVS picks the minimum version that satisfies all constraints in the build list.

Your module requires A v1.2
B requires A v1.5
C requires A v1.3
----------------------------
Selected A = v1.5  (max of minima)

Properties:

  • Reproducible given the same go.mod build list
  • Not “always latest”
  • Upgrades are explicit (go get pkg@vX)
go list -m all
go list -m -versions github.com/some/dep
go get example.com/dep@v1.4.2
go get example.com/dep@none   # drop

The Build List

go list -m all shows the selected set. Understanding diamond dependencies is mostly reading that list and go mod graph | grep dep.

replace and exclude

replace example.com/fork => ../fork

exclude example.com/bad v1.0.0

replace is powerful for local development; avoid permanent replaces in published modules — consumers inherit pain. Prefer releasing fixed versions.

Retracts

Module authors can retract broken versions so go get avoids them. If you cannot upgrade, pin carefully and track advisories.

Toolchain Directive

Modern Go can select a toolchain version:

go 1.27
toolchain go1.27.1
go env GOTOOLCHAIN  # auto | local | path | version

CI should pin or intentionally use auto with care so builds do not silently jump compilers mid-incident.

Vendoring

go mod vendor
go build -mod=vendor

Pros: offline builds, airgapped, reviewable third-party diffs. Cons: repo size, merge noise. Good for enterprises and release branches.

Checksums and Proxies

GOPROXY  (default proxy.golang.org)
GOSUMDB  (sum.golang.org)
GOPRIVATE / GONOSUMDB for private modules
export GOPRIVATE=github.com/myorg/*

Private modules need auth to the host; sumdb is skipped for GOPRIVATE.

Diagnosis Playbook

Symptom Action
“missing go.sum entry” go mod tidy
Unexpected old dep go mod graph + who requires it
Local replace forgotten Search replace in go.mod
CI != laptop Print go version, go env, go list -m all
Ambiguous import Module path vs directory mismatch

Experiment

mkdir /tmp/mvs-demo && cd /tmp/mvs-demo
go mod init example.com/demo
go get rsc.io/quote@v1.5.2
go list -m all
go mod graph | head
cat go.mod
go mod tidy

What to notice: go.mod records the selected versions; go.sum grows with hashes; graph shows edges that forced versions upward.

Try next: Introduce a replace to a local stub module and build; then remove replace and fetch the real version.