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

  1. Serialize go commands: run one to completion before starting another in the same module.
  2. Disable editor auto-format/save hooks that touch go.mod during builds.
  3. Re-run the command after the concurrent one finishes (the fresh read picks up the new contents).
  4. 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

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


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/f12663f45ed942b1. Report an issue: GitHub.