go.mod, go.sum & Minimal Version Selection
go.mod declares your module and its dependencies; go.sum locks their checksums.
Together they give reproducible builds, and Minimal Version Selection (MVS) picks compatible versions without arbitrary "newest wins" behavior.
Summary
go.mod is the authoritative dependency contract for a module.
go.sum records h1 hashes of module archives and go.mod files the toolchain has seen.
MVS reads every requirement in the graph and selects the minimum semver version that still satisfies them all.
Understanding those three pieces explains most go get, CI drift, and "why is this version here?" questions.
Recipe
Quick-reference recipe card - copy-paste ready.
go mod init example.com/app
go get github.com/stretchr/testify@v1.9.0
go mod tidy
go list -m all// go.mod (after tidy)
module example.com/app
go 1.26
require (
github.com/stretchr/testify v1.9.0
)When to reach for this:
- Bootstrapping a new service or library module.
- Auditing which versions CI actually builds against.
- Cleaning up unused
requirelines after a refactor (go mod tidy). - Investigating a transitive dependency bump.
Working Example
example.com/app/
go.mod
go.sum
main.go
// main.go
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
fmt.Println(uuid.NewString())
}go get github.com/google/uuid@v1.6.0
go mod tidy
cat go.mod
grep uuid go.sumWhat this demonstrates:
requirerecords direct dependencies you import (and sometimes tooling).go.sumgains lines likegithub.com/google/uuid v1.6.0 h1:...for content verification.go list -m allshows direct and transitive modules in the build list.
Deep Dive
How It Works
- You add or change imports (or run
go get). - The
gocommand loadsgo.modfiles for your module and dependencies recursively. - MVS computes a version per module path that satisfies every
requireedge. - Downloads are verified against
go.sum; missing lines may be added on first trusted fetch. - Builds compile packages from the module cache at those versions.
MVS is not a global solver that minimizes total upgrades.
It is local per module path: pick the oldest version that still meets all stated minimums.
go.mod Directives
| Directive | Purpose |
|---|---|
module | Import path prefix for this module |
go | Minimum Go toolchain/language version |
require | Module path → semver (or pseudo-version) |
exclude | Prevent selecting a specific bad version |
replace | Override source or version (local path, fork) |
retract | Mark your own bad releases (in your module's go.mod) |
Indirect dependencies appear with // indirect comments after go mod tidy when you do not import them directly.
go.sum Lines
Each module version typically has:
github.com/foo/bar v1.2.3 h1:<hash>
github.com/foo/bar v1.2.3/go.mod h1:<hash>
The /go.mod line hashes the dependency's own go.mod file.
Commit go.sum for applications and libraries so CI verifies the same content.
MVS Example
If module A requires C v1.2.0 and module B requires C v1.4.0, MVS selects C v1.4.0 (the minimum that satisfies the higher requirement).
If both required v1.2.0, you get v1.2.0 even when v1.9.0 exists on the proxy.
Go Notes
# See why a module appears
go mod why -m github.com/some/transitive
# Graph of requirements
go mod graph | head
# Verify sums without network when possible
go mod verify// toolchain directive (optional, Go 1.21+)
// go.mod
module example.com/app
go 1.26
toolchain go1.26.0Gotchas
- Editing
go.modby hand withoutgo mod tidyleaves orphan requires. Prefer commands that update bothgo.modandgo.sum. - Assuming
go get -ualways helps can introduce broad upgrades; review diffs and run tests. - Deleting
go.sumto "fix" errors removes audit trail; regenerate with tidy and intentional gets instead. // indirectdoes not mean unused - it means no package in your module imports it directly; you may still need it transitively.- Private modules need
GOPRIVATEand VCS credentials - checksum DB may be skipped for private paths.
Alternatives
- Vendor (
go mod vendor) - Vendor source intovendor/for offline or hermetic CI. - Workspace (
go.work) - Local overrides without committingreplaceto every module. - Pin with
go get module@vin CI - Scripted bumps instead of floating@lateston main.
FAQs
What is Minimal Version Selection?
An algorithm that chooses, for each module path, the lowest semver version that satisfies all require directives in the combined graph.
Should go.sum be in .gitignore?
No for team projects.
Ignore only in throwaway experiments; production repos commit it.
Why did go mod tidy add an indirect require?
A direct dependency imports another module you do not import yourself.
Tidy records it so builds stay reproducible.
What does go mod verify do?
Checks cached module trees and sums against go.sum.
Run in CI to detect cache corruption or tampering.
How do I downgrade a dependency?
go get github.com/foo/bar@v1.1.0 then test and commit the go.mod/go.sum diff.
Does MVS pick pre-release versions?
Only when required explicitly or when no stable version satisfies constraints.
Prefer stable tags for production.
What is the checksum database?
A public log (sum.golang.org) mirroring module hashes for open modules.
Private modules set GOPRIVATE to skip it.
Can two go.mod files disagree?
Your module's build merges the graph; MVS resolves to compatible versions or fails with an error during load.
What does the go directive control?
Language features and toolchain behavior available when building this module.
It is not a runtime dependency like npm engines alone.
How often should I run go mod tidy?
After merges that touch imports, before release tags, and in CI on every PR.
Related
- Packages and Modules: Go's Unit of Distribution - Big picture for modules
- Pseudo-Versions, replace & retract Directives - Overrides beyond require
- go work: Workspace Mode for Multi-Module Repos - Local multi-module builds
- Packages & Modules Basics - tidy, get, and list commands
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).