Skip to content

go mod init — Find the Bug

Each snippet contains a real-world bug related to initialising and maintaining Go modules. Find it, explain it, fix it.


Bug 1 — Uppercase letters in module path

$ go mod init github.com/Acme/MyApp
module github.com/Acme/MyApp

go 1.22

Bug: Module paths are case-sensitive and the proxy normalises differently from Git. Uppercase characters force !-escaping in $GOPATH/pkg/mod (github.com/!acme/!my!app) and break case-insensitive filesystems on macOS/Windows. Two collaborators with different OSes will produce different go.sum entries.

Fix: use a lowercase path and rename the GitHub repo if needed:

$ go mod init github.com/acme/myapp

Bug 2 — Missing dot in module path

$ go mod init myproject
module myproject

go 1.22

Bug: Without a dot in the first path element, Go treats the module as private/local. Anyone trying to go get myproject fails with "malformed module path: missing dot in first path element." You can never publish, import from another project, or use replace cleanly.

Fix: use a real domain (or a known sentinel like example.com):

$ go mod init github.com/yourname/myproject

For throwaway scratch work, example.com/myproject is acceptable.


Bug 3 — Running go mod init in the wrong directory

$ pwd
/Users/me/code/myapp/cmd/server
$ go mod init github.com/me/myapp
$ ls
go.mod  main.go

Bug: The module was initialised inside cmd/server, not at the project root. Now cmd/server is its own module. Imports like github.com/me/myapp/internal/db resolve to nothing because the rest of the tree is outside the module.

Fix: delete the misplaced go.mod, go to the project root, and re-init:

$ rm go.mod
$ cd /Users/me/code/myapp
$ go mod init github.com/me/myapp

Bug 4 — Re-running go mod init and losing dependencies

$ ls
go.mod  go.sum  main.go
$ go mod init github.com/me/myapp   # I just want to "reset" it
go: /Users/me/code/myapp/go.mod already exists
$ rm go.mod go.sum
$ go mod init github.com/me/myapp
$ go build
main.go:5:8: no required module provides package github.com/spf13/cobra

Bug: Deleting go.mod and go.sum to "reinitialise" wipes every require, replace, and pinned version. The next build re-resolves everything from scratch — dropping pins, breaking reproducibility, possibly pulling in newer (or yanked) versions.

Fix: never re-init an existing module. To change the path, edit go.mod directly or run:

$ go mod edit -module=github.com/me/newname

To refresh the dep graph without losing pins:

$ go mod tidy

Bug 5 — Missing go directive

module github.com/me/myapp

require github.com/spf13/cobra v1.8.0

Bug: No go directive. go mod init always writes one, so this file was hand-edited or generated by an old/broken tool. Without it, the toolchain assumes go 1.16 semantics, which disables module-graph pruning and lazy loading. Builds slow down and behaviour subtly differs.

Fix: declare your minimum Go version:

module github.com/me/myapp

go 1.22

require github.com/spf13/cobra v1.8.0

Bug 6 — go directive higher than installed toolchain

module github.com/me/myapp

go 1.99
$ go build
go: module requires Go 1.99

Bug: Either someone bumped go 1.22 to go 1.99 to "future-proof" it, or copy-pasted from an unrelated module. The CI image only has Go 1.22 installed, so every pipeline now fails.

Fix: set the directive to the minimum version you actually need, not the maximum you can imagine:

go 1.22

If you genuinely need a newer toolchain, also add:

toolchain go1.23.2

so Go can auto-download it instead of erroring.


Bug 7 — Major-version path mismatch

module github.com/me/awesome

go 1.22
$ git tag v2.0.0
$ git push --tags
$ # consumer
$ go get github.com/me/awesome@v2.0.0
go: github.com/me/awesome@v2.0.0: invalid version: module contains a go.mod
file, so major version must be compatible: should be v0 or v1, not v2

Bug: Semantic Import Versioning rule: any major version v2+ must include the major suffix in the module path. The go.mod still says github.com/me/awesome, so Go refuses to publish it as v2.

Fix: edit go.mod to include /v2 and update all internal imports:

module github.com/me/awesome/v2

Then re-tag:

$ git tag v2.0.0

Bug 8 — Two go.mod files in the same project

$ find . -name go.mod
./go.mod
./internal/tools/go.mod

Bug: Someone ran go mod init inside internal/tools to "isolate" a script. Now internal/tools is a sub-module — packages from the outer module cannot import it via the normal path, and go build ./... from the root silently skips it.

Fix: decide intentionally. If internal/tools is meant to be part of the main module, delete its go.mod. If it should be separate (multi-module repo), use a go.work file at the root:

$ go work init . ./internal/tools

Bug 9 — replace directive leaking into a release

module github.com/me/api

go 1.22

require github.com/me/shared v1.4.0

replace github.com/me/shared => ../shared

Bug: The replace ... => ../shared worked locally because the developer had both repos checked out. Once tagged and published, every consumer fails with: "replacement directory ../shared does not exist." replace directives apply only to the main module — but they still poison releases by signalling untrusted state.

Fix: remove replace before tagging, or move it into a local-only go.work:

go 1.22

use (
    .
    ../shared
)

Commit go.mod without replace; keep go.work ignored or workspace-only.


Bug 10 — vendor/modules.txt out of sync

$ go build
go: inconsistent vendoring in /Users/me/code/myapp:
    github.com/spf13/cobra@v1.8.0: is explicitly required in go.mod, but
    not marked as explicit in vendor/modules.txt

Bug: Someone edited go.mod (added a require), or hand-edited vendor/, without re-running go mod vendor. The two views of the dep graph disagree.

Fix: regenerate the vendor tree:

$ go mod tidy
$ go mod vendor
$ git add go.mod go.sum vendor

Never edit vendor/modules.txt by hand.


Bug 11 — Module path with .git suffix

$ go mod init github.com/me/myapp.git
module github.com/me/myapp.git

Bug: Most people copy the SSH/HTTPS clone URL (git@github.com:me/myapp.git or https://github.com/me/myapp.git) and reuse it as a module path. Go does not strip the .git, so importers must write import "github.com/me/myapp.git/foo" — ugly and inconsistent with everyone else's repos.

Fix: drop the .git:

$ go mod edit -module=github.com/me/myapp

Update internal imports accordingly.


Bug 12 — Trailing slash in module path

module github.com/me/myapp/

go 1.22

Bug: A trailing slash silently corrupts the path. Some tools accept it; others (the proxy, go list -m) treat the module as a different identity. Imports may resolve weirdly, and go.sum entries become unstable.

Fix: remove the trailing slash:

module github.com/me/myapp

Bug 13 — Wrong-cased import path

import "github.com/Sirupsen/logrus"   // old import path
$ go build
go: github.com/sirupsen/logrus@v1.9.0: github.com/Sirupsen/logrus@v1.9.0:
parsing go.mod: unexpected module path "github.com/sirupsen/logrus"

Bug: The repository was renamed from Sirupsen to sirupsen years ago. On case-insensitive filesystems, the old path appears to work locally, but the proxy and go.sum reject it.

Fix: update every import to the canonical lowercase form:

import "github.com/sirupsen/logrus"

Then run go mod tidy.


Bug 14 — go.mod committed without go.sum

$ git ls-files | grep -E 'go\.(mod|sum)'
go.mod
$ go build
verifying github.com/spf13/cobra@v1.8.0: missing go.sum entry; to add it:
    go mod download github.com/spf13/cobra

Bug: Someone added go.sum to .gitignore thinking it was a generated artifact. Without it, every clean checkout has to re-derive checksums and a malicious proxy could silently substitute code.

Fix: always commit go.sum. Remove it from .gitignore, run go mod tidy, and commit:

$ go mod tidy
$ git add go.sum
$ git commit -m "Track go.sum"

Bug 15 — Skipping go mod tidy after edits

// added a new import
import "github.com/google/uuid"
$ go build
main.go:5:8: no required module provides package github.com/google/uuid;
to add it:
    go get github.com/google/uuid

Bug: Editing source code does not automatically update go.mod. Many teams hit this in CI when one developer ran go run locally (which adds requires implicitly in some workflows) but never committed the result.

Fix: make go mod tidy part of your pre-commit and CI checks:

$ go mod tidy
$ git diff --exit-code go.mod go.sum   # CI fails if these changed

Bug 16 — Non-ASCII or whitespace in module path

$ go mod init "github.com/me/my app"

or

$ go mod init github.com/me/résumé

Bug: The Go module system requires module paths to match a strict syntax: ASCII letters, digits, ., -, _, /, plus a few escapes. Spaces, accents, or emoji are rejected by the proxy, by go get, or — worse — accepted locally and broken remotely.

Fix: use plain ASCII, kebab- or snake-case:

$ go mod init github.com/me/my-app

Bug 17 — Vanity URL with a malformed meta tag

The team owns go.acme.dev/api and wants it to forward to GitHub. The HTML returned by https://go.acme.dev/api?go-get=1:

<meta name="go-import" content="go.acme.dev/api github.com/acme/api">
$ go get go.acme.dev/api
go: go.acme.dev/api: unrecognized import path "go.acme.dev/api":
parse https://go.acme.dev/api?go-get=1: meta tag missing VCS field

Bug: The go-import meta tag must contain three space-separated fields: <import-prefix> <vcs> <repo-url>. The VCS (git) is missing.

Fix:

<meta name="go-import"
      content="go.acme.dev/api git https://github.com/acme/api">

Re-deploy and verify with curl:

$ curl -sS 'https://go.acme.dev/api?go-get=1' | grep go-import

Bug 18 — go.work accidentally committed

$ git status
On branch main
Changes to be committed:
    new file:   go.work
    new file:   go.work.sum

Bug: go.work is meant for local multi-module development. Once committed, every consumer who clones the repo gets workspace mode forced on, which silently overrides go.mod versions with whatever path the workspace points at — usually broken on their machine.

Fix: keep workspaces local. Add to .gitignore:

go.work
go.work.sum

If you genuinely have a multi-module monorepo and want workspaces shared, document it explicitly and have CI verify the file is consistent — but the default is "do not commit."


Bug 19 — GOPRIVATE not set for internal modules

$ go get git.acme.internal/team/utils
go: git.acme.internal/team/utils@v1.2.0: reading
https://proxy.golang.org/git.acme.internal/team/utils/@v/v1.2.0.info:
410 Gone
verifying git.acme.internal/team/utils@v1.2.0: checksum database lookup
required for non-public module

Bug: By default Go fetches via proxy.golang.org and verifies via sum.golang.org. Both are public — they cannot see internal hosts. Without GOPRIVATE, every internal-only dep fails the checksum-DB lookup.

Fix: mark the internal namespace as private:

$ go env -w GOPRIVATE=git.acme.internal,*.acme.internal

For finer control:

$ go env -w GONOPROXY=git.acme.internal
$ go env -w GONOSUMCHECK=git.acme.internal

Commit a Makefile or tools.sh that sets these so new hires do not hit the same wall.


Bug 20 — Initialising the module in $HOME

$ pwd
/Users/me
$ go mod init github.com/me/myapp
go: creating new go.mod: module github.com/me/myapp

Bug: A go.mod now lives at $HOME. Every go build, go test, or go run issued from any subdirectory of $HOME thinks it is inside this module. Random scripts, dotfile experiments, and other repos behave bizarrely until the rogue go.mod is removed.

Fix: delete the home-level go.mod immediately:

$ rm /Users/me/go.mod /Users/me/go.sum

Then create a real project directory and go mod init from there.


Bug 21 — Stale go.sum after a manual upgrade

require github.com/spf13/cobra v1.9.0
github.com/spf13/cobra v1.8.0 h1:...
github.com/spf13/cobra v1.8.0/go.mod h1:...
$ go build
verifying github.com/spf13/cobra@v1.9.0: missing go.sum entry; for more
information, run 'go mod download github.com/spf13/cobra'

Bug: Someone hand-edited go.mod to bump cobra from v1.8.0 to v1.9.0 but did not regenerate go.sum. The toolchain now refuses to use a version it cannot verify.

Fix: never edit go.mod versions by hand for upgrades. Use go get:

$ go get github.com/spf13/cobra@v1.9.0
$ go mod tidy

This atomically updates go.mod and go.sum.


Bug 22 — Adding replace for a fork without pinning a commit

require github.com/lib/foo v1.0.0

replace github.com/lib/foo => github.com/me/foo-fork latest

Bug: replace does not accept latest. It needs a real version, a pseudo-version, or a local path. The build either fails or, on older toolchains, silently picks something unexpected.

Fix: point at a real tag or commit:

$ go mod edit -replace=github.com/lib/foo=github.com/me/foo-fork@v1.0.1-fix
$ go mod tidy

For an unreleased commit, use the pseudo-version Go computes:

$ go get github.com/me/foo-fork@<sha>

Bug 23 — Module path matches a typo-squatted package

$ go mod init github.com/me/myapp
$ go get github.com/sirrupsen/logrus    # note the extra 'r'

Bug: Typo squatting is a real attack vector. sirrupsen is not sirupsen. The build "works" because something exists at that path — possibly a malicious mirror.

Fix: copy import paths from the official repo, never type them. Lock dependencies with go.sum, audit with go list -m all, and consider tools like govulncheck and osv-scanner.


Bug 24 — Forgetting to commit go.mod after go mod init

$ go mod init github.com/me/myapp
$ git add main.go
$ git commit -m "Initial commit"
$ git push
# CI:
$ go build
go: cannot find main module, but found .git/config in /workspace
    to create a module there, run:
    go mod init

Bug: go.mod was never staged. Locally everything works because the file exists; CI clones a repo without it and immediately fails.

Fix: include both files in the very first commit:

$ git add go.mod go.sum
$ git commit -m "Add Go module files"
$ git push

Add a CI step that fails fast if go.mod is missing:

$ test -f go.mod || { echo "missing go.mod"; exit 1; }

Bug 25 — go mod init with a path that already has a release

$ go mod init github.com/popular/project

Bug: Picking a module path you do not control is a recipe for disaster. The path collides with an existing module on the proxy. Anyone who imports your repo gets the other project's code from cache, and CI fails with "verifying ...: checksum mismatch" if they ever clear it.

Fix: module paths must be unique and owned by you. Use your own GitHub/GitLab namespace, or a vanity domain you control. If you are forking, append a suffix or use the /v2 major-version mechanism on your own path.


Summary

A working go.mod is the contract between your repo, your collaborators, the proxy, and every consumer that ever imports your code. Most module bugs come from one of three sins:

  1. Treating go.mod like an editable text file. Use go mod edit, go get, go mod tidy — let the tooling keep go.mod and go.sum consistent.
  2. Conflating local convenience with a published artifact. replace, go.work, and home-directory experiments are local-only; never let them leak into a tag.
  3. Skipping CI checks. A pipeline that runs go mod tidy and git diff --exit-code catches almost every entry on this page before it reaches main.

Adopt those three habits and the rest of the module system becomes mostly invisible.