golang/go · error
existing contents have changed since last read
Error message
existing contents have changed since last read
What it means
During go.mod update, modload held a cached copy (index.data) of the file but on the write path the on-disk bytes ('old') differ from the cached snapshot, meaning a concurrent process edited go.mod between read and write. Rather than reconcile, the command fails fast to preserve consistency.
Source
Thrown at src/cmd/go/internal/modload/init.go:2092
if unlock, err := modfetch.SideLock(ctx); err == nil {
defer unlock()
}
err = lockedfile.Transform(modFilePath, func(old []byte) ([]byte, error) {
if bytes.Equal(old, updatedGoMod) {
// The go.mod file is already equal to new, possibly as the result of some
// other process.
return nil, errNoChange
}
if index != nil && !bytes.Equal(old, index.data) {
// The contents of the go.mod file have changed. In theory we could add all
// of the new modules to the build list, recompute, and check whether any
// module in *our* build list got bumped to a different version, but that's
// a lot of work for marginal benefit. Instead, fail the command: if users
// want to run concurrent commands, they need to start with a complete,
// consistent module definition.
return nil, fmt.Errorf("existing contents have changed since last read")
}
return updatedGoMod, nil
})
if err != nil && err != errNoChange {
return fmt.Errorf("updating go.mod: %w", err)
}
return nil
}
// keepSums returns the set of modules (and go.mod file entries) for which
// checksums would be needed in order to reload the same set of packages
// loaded by the most recent call to LoadPackages or ImportFromFiles,
// including any go.mod files needed to reconstruct the MVS result
// or identify go versions,
// in addition to the checksums for every module in keepMods.
func keepSums(ld *Loader, ctx context.Context, pld *packageLoader, rs *Requirements, which whichSums) map[module.Version]bool {View on GitHub (pinned to b6b368adc5)
Solutions
- Serialize go commands: run one to completion before starting another in the same module.
- Disable editor auto-format/save hooks that touch go.mod during builds.
- Re-run the command after the concurrent one finishes (the fresh read picks up the new contents).
- Use a workspace (go.work) or separate checkouts to allow parallelism without contention.
Example fix
// before $ go mod tidy & go build ./... // concurrent go.mod writers // updating go.mod: existing contents have changed since last read // after $ go mod tidy && go build ./... // sequential
Defensive patterns
Strategy: validation
Validate before calling
// Detect concurrent go.mod writers before running builds (best-effort file lock).
f, err := os.OpenFile("go.mod", os.O_RDWR, 0644)
if err != nil { return err }
defer f.Close()
if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
return errors.New("go.mod is locked by another go command")
}
// proceed with the build Try / catch
out, err := exec.Command("go", "mod", "tidy").CombinedOutput()
if err != nil && bytes.Contains(out, []byte("existing contents have changed since last read")) {
// wait for the other writer to finish, then retry ONCE with fresh state
time.Sleep(2 * time.Second) // only because we cannot observe the lock directly here
out, err = exec.Command("go", "mod", "tidy").CombinedOutput()
}
return err Prevention
- Do not run multiple go commands that write go.mod in parallel.
- Disable IDE auto-tidy-on-save during terminal builds.
- Use 'go work' or separate checkouts for parallel workflows.
- Pre-commit hooks should run sequentially, not concurrently with builds.
When it happens
Trigger: Two 'go' commands (or an editor / hook) modified go.mod concurrently; the current command read go.mod, planned an update, and on re-read for write found different bytes than index.data.
Common situations: Running 'go mod tidy' and 'go build' in parallel; an IDE auto-running goimports/go mod while a terminal command runs; a file watcher / pre-commit hook rewriting go.mod mid-operation.
Related errors
- updating go.mod: %w
- inode for file changed since last Lock or RLock
- ${GoModToolVersion} is required for tool directives in go.mo
- updates to go.mod needed, but go.mod is part of the overlay
- disallowed module version
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/f12663f45ed942b1.
Report an issue: GitHub.