skip to content

A pull request's go.mod lost a require line after `go mod tidy` — how do you decide it is safe?

level: seniorimportance: should knowfreq 40%

answer

  1. tidy removes only what nothing reaches
  2. look for the import that left in the same diff
  3. reproduce it: tidy again, expect no change
  4. a dropped require can lower another version
  5. compare the build lists, not just the file

basics

~20 s

Confirm the reason: tidy drops a requirement only when nothing in the module reaches that module any more, so look for the matching import removal in the same diff. Then re-run tidy yourself, check go mod why -m, and diff the build list before and after.

solid answer

~50 s

Start from the rule tidy follows: a requirement is removed only when no package or test in the main module, under any build tag or platform, needs that module — and nothing else in the graph needs it either. So the honest diff has a matching import removal in the same pull request. The checks I run as a reviewer are: pull the branch and run `go mod tidy` with the repository's Go version and confirm it produces no further change; run `go mod why -m` on the removed module and expect "main module does not need module"; and diff `go list -m all` between the base and the branch, because dropping a require — especially an indirect one that was acting as a floor — can lower the selected version of something else. Build and test for every target platform, not just the reviewer's. If the go or toolchain line moved in the same diff, treat the whole require block as re-derived and review it as such.

code

text · 8 lines
text
$ go mod why -m example.com/httpx
# example.com/httpx
(main module does not need module example.com/httpx)

$ go mod why -m example.com/text
# example.com/text
example.com/billing/report
example.com/text/message

go deeper

for a junior

Know that go.mod is part of the change under review and that a removed require should have a removed import next to it. Ask the author what dropped it rather than approving on faith.

for a middle

Explain the rule tidy applies and reproduce it: run tidy on the branch, expect no further change, and use go mod why -m to confirm nothing still reaches the module.

for a senior

Demonstrate the side-effect check — diffing the resolved build list across the branch to catch a version that moved down — and insist on the full platform matrix rather than one local build.

for a principal

Own the pipeline that makes this cheap: a fixed toolchain so tidy is deterministic, a CI tidiness gate, a platform matrix, and a published build-list diff so dependency changes are visible without a reviewer hunting for them.

## Why the question is not rhetorical `go.mod` diffs are the part of a pull request people skim. They look like generated noise, and mostly they are — but a removed `require` line is one of the few module-level changes that can alter what compiles without anyone editing a line of Go. Reviewing it well is a small, learnable procedure. ## The rule the tool followed `go mod tidy` removes a requirement when **no package in the main module, and no test of one, needs that module** — evaluated across build tags and target platforms, and including modules named by `tool` directives. So a removal is a claim: *nothing reaches this any more.* Your job is to decide whether that claim is true for the code as it will exist on the main branch. ## The four checks **1. Find the cause in the same diff.** A removed direct requirement should pair with a removed import. If the pull request deletes `import "example.com/httpx/client"` and the `example.com/httpx` require disappears, the story is complete. If no import left, something else explains it, and you need to know what. **2. Reproduce it.** Check out the branch and run `go mod tidy` with the Go version the repository expects, then `git status`. If tidy produces further changes, what was committed is not a tidy result — it was hand-edited, produced by a different toolchain, or produced from a different working tree. **3. Ask who needs it.** `go mod why -m example.com/httpx` should print `(main module does not need module example.com/httpx)`. If instead it prints a chain of packages, the requirement is still reachable and the removal will not survive the next tidy — or worse, the branch was tidied with a file missing. **4. Diff the build list.** This is the check people skip and the one that catches real breakage: ``` git checkout main && go list -m all > before.txt git checkout branch && go list -m all > after.txt diff before.txt after.txt ``` A removed *direct* requirement usually shows up here as that module leaving the list — expected. A removed **indirect** requirement is the interesting case: an indirect line records a minimum version, and deleting it can let some other module resolve **lower** than it did before. A build list diff that shows an unrelated module moving down is the signal to stop and ask why, because a silent downgrade re-introduces bugs the team already fixed. ## Innocent explanations that are not the visible import When no import left the diff, the usual causes are: - the `go` line changed in the same pull request, so the requirements were re-derived under different rules; - a build-tagged file, or a whole test file, was deleted, taking the only import of that module with it; - a `tool` directive was removed, so an executable dependency is no longer tracked; - the branch was tidied by a different Go toolchain than everyone else uses. All four are legitimate; each one changes what you should check next, which is why identifying the cause comes before approving. ## Build it the way it will ship Tidy reasons about every platform; a local `go build ./...` reasons about one. If the service ships for a different GOOS or GOARCH than the reviewer's laptop, or has code behind build tags, run the build for those targets — `GOOS=linux GOARCH=arm64 go build ./...` — or rely on a CI matrix that does. "It compiles for me" is not evidence about the code paths tidy considered. ## Make it stop being a judgment call The durable fix is to remove the ambiguity from review entirely: - CI runs `go mod tidy` and fails when go.mod or go.sum then differ, so every committed module file is provably tidy; - the Go version used in CI is fixed, so tidy is deterministic across contributors; - CI compiles and tests the full platform matrix, so tidy's broader view is actually exercised; - optionally, CI prints the `go list -m all` diff against the base branch on every pull request, which turns "did any version move?" from an investigation into a line in the build log. With those in place, a lost `require` line becomes a one-glance review: the import is gone, the build list moved the way it should, and the pipeline agrees.

  • What would make you distrust a go.mod diff even though the branch compiles on your machine?
    That it compiles for one GOOS and GOARCH. Tidy reasons about every platform and build tag, plus tests, so a requirement can be needed by code your local build never touches. I want the full platform matrix green, or I re-run tidy myself and expect an empty diff.
  • A require line vanished but nobody removed an import. What explains that?
    Usually one of: the `go` line changed in the same pull request so requirements were re-derived; a build-tagged or test file that held the only import was deleted; a `tool` directive was removed; or the branch was tidied by a different Go toolchain than the team uses. Each needs a different follow-up.
  • Why is a removed indirect requirement riskier to wave through than a removed direct one?
    An indirect line records a minimum version. Deleting it can let another module resolve to a lower version than before, so the change is not just "one fewer dependency" — it can quietly downgrade something. Diffing `go list -m all` across the branch surfaces that immediately.
  • How do you keep this from being a manual review step forever?
    Have CI run `go mod tidy` with a fixed Go version and fail on any resulting diff, build and test the full platform matrix, and print the `go list -m all` difference against the base branch. Then the pull request either shows a clean, explained module change or it fails.

saying these in an interview costs you the question

  • Approves because the build is green on one machine
  • Assumes removing a require cannot change any other version
  • Treats every go.mod diff as generated churn to skim
  • Re-adds the require by hand instead of finding the import
  • Never re-runs tidy to reproduce the committed result