golang/go · error
updates to go.mod needed, but go.mod is part of the overlay
Error message
updates to go.mod needed, but go.mod is part of the overlay specified with -overlay
What it means
With `-overlay`, the go command reads go.mod from a substitute file. If the build determines go.mod needs to be rewritten (dirty=true) but go.mod itself is part of the overlay (fsys.Replaced returns true), it refuses to write because it cannot propagate changes back into an overlay-mapped file. The command aborts to avoid a silently stale go.mod.
Source
Thrown at src/cmd/go/internal/modload/init.go:2055
if !dirty && cfg.CmdName != "mod tidy" {
// The go.mod file has the same semantic content that it had before
// (but not necessarily the same exact bytes).
// Don't write go.mod, but write go.sum in case we added or trimmed sums.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
if err := ld.Fetcher().WriteGoSum(ctx, keepSums(ld, ctx, ld.pkgLoader, ld.requirements, addBuildListZipSums), mustHaveCompleteRequirements(ld)); err != nil {
return err
}
}
return nil
}
mainModule := ld.MainModules.mustGetSingleMainModule(ld)
modFilePath := modFilePath(ld.MainModules.ModRoot(mainModule))
if fsys.Replaced(modFilePath) {
if dirty {
return errors.New("updates to go.mod needed, but go.mod is part of the overlay specified with -overlay")
}
return nil
}
defer func() {
// At this point we have determined to make the go.mod file on disk equal to new.
ld.MainModules.SetIndex(mainModule, indexModFile(updatedGoMod, modFile, mainModule, false))
// Update go.sum after releasing the side lock and refreshing the index.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
if err == nil {
err = ld.Fetcher().WriteGoSum(ctx, keepSums(ld, ctx, ld.pkgLoader, ld.requirements, addBuildListZipSums), mustHaveCompleteRequirements(ld))
}
}
}()
// Make a best-effort attempt to acquire the side lock, only to exclude
// previous versions of the 'go' command from making simultaneous edits.View on GitHub (pinned to b6b368adc5)
Solutions
- Remove the go.mod entry from the overlay JSON so the real on-disk file is used.
- Run the command without `-overlay` when it needs to update go.mod.
- Force read-only mode (-mod=readonly) so no go.mod write is attempted.
Example fix
// before overlay.json
// {"Replace": {"/mod/go.mod": "/tmp/generated-go.mod"}}
// cmd: go get -overlay overlay.json example.com/x@latest
// after overlay.json (go.mod removed)
// {"Replace": {}}
// cmd: go get example.com/x@latest Defensive patterns
Strategy: validation
Validate before calling
// Ensure go.mod is NOT covered by the -overlay before running a mutating command.
func overlayExcludesGoMod(overlayPath string) error {
b, err := os.ReadFile(overlayPath)
if err != nil { return err }
if bytes.Contains(b, []byte("go.mod")) {
return fmt.Errorf("overlay includes go.mod; remove it before go get/mod tidy")
}
return nil
} Type guard
null
Try / catch
null
Prevention
- Scope overlays to source files only, never go.mod/go.sum.
- When writing build tooling, strip go.mod from generated overlays.
- Prefer -mod=readonly for read-only overlay-driven checks.
When it happens
Trigger: Any command that mutates go.mod (go get, go mod tidy/edit, build with -mod=mod) run together with `-overlay=<file>` whose JSON maps the module's go.mod path to another file.
Common situations: Editor/LSP/gopls or build tools that build a synthetic overlay and accidentally include go.mod; CI that overlays the whole module directory.
Related errors
- deleted in overlay
- cannot open directory in overlay
- ${GoModToolVersion} is required for tool directives in go.mo
- disallowed module version
- parsing go.mod: missing module line
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/859b6e206fc9b52a.
Report an issue: GitHub.