golang/go · error

error: size of %s changed during reading (from %d to %d byte

Error message

error: size of %s changed during reading (from %d to %d bytes)

What it means

Thrown by gofmt's source reader when io.ReadFull returns fewer bytes (n) than the size recorded by an earlier os.Stat (size), meaning the file shrank between Stat and the read. gofmt pre-allocates src as size+1 bytes and treats n<size as a concurrent modification that would corrupt formatting, so it bails rather than emit wrong output.

Source

Thrown at src/cmd/gofmt/gofmt.go:359

	// We try to read size+1 bytes so that we can detect modifications: if we
	// read more than size bytes, then the file was modified concurrently.
	// (If that happens, we could, say, append to src to finish the read, or
	// proceed with a truncated buffer — but the fact that it changed at all
	// indicates a possible race with someone editing the file, so we prefer to
	// stop to avoid corrupting it.)
	src := make([]byte, size+1)
	n, err := io.ReadFull(in, src)
	switch err {
	case nil, io.EOF, io.ErrUnexpectedEOF:
		// io.ReadFull returns io.EOF (for an empty file) or io.ErrUnexpectedEOF
		// (for a non-empty file) if the file was changed unexpectedly. Continue
		// with comparing file sizes in those cases.
	default:
		return nil, err
	}
	if n < size {
		return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n)
	} else if n > size {
		return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src))
	}
	return src[:n], nil
}

func main() {
	// Arbitrarily limit in-flight work to 2MiB times the number of threads.
	//
	// The actual overhead for the parse tree and output will depend on the
	// specifics of the file, but this at least keeps the footprint of the process
	// roughly proportional to GOMAXPROCS.
	maxWeight := (2 << 20) * int64(runtime.GOMAXPROCS(0))
	s := newSequencer(maxWeight, os.Stdout, os.Stderr)

	// call gofmtMain in a separate function
	// so that it can use defer and have them
	// run before the exit.

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Re-run gofmt once the file is stable — the condition is transient.
  2. Stop concurrent writers (close the editor's auto-format, pause `go generate`) while gofmt runs.
  3. For CI, run gofmt on a clean checkout without parallel generation steps.
  4. If persistent, check for a misbehaving watcher/formatter loop that keeps truncating the file.
Defensive patterns

Strategy: retry

Try / catch

// run gofmt with a bounded retry on transient size-change errors:
for i := 0; i < 3; i++ {
    out, err := exec.Command("gofmt", file).CombinedOutput()
    if err == nil { break }
    if !strings.Contains(string(out), "changed during reading") { break }
    time.Sleep(100 * time.Millisecond << i)
}

Prevention

When it happens

Trigger: Another process truncates or rewrites the file shorter between the initial Stat (which sets `size`) and io.ReadFull. Common with editors auto-saving, formatters racing, build tools rewriting generated files, or version-control operations happening during gofmt.

Common situations: Running gofmt in a tight watch loop while an editor saves; piping through gofmt while a code generator regenerates the same file; CI running gofmt concurrently with `go generate`; NFS/network filesystems with eventual consistency.

Related errors


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