golang/go · error

short write writing body to GOCACHEPROG for action %x, outpu

Error message

short write writing body to GOCACHEPROG for action %x, output %x: wrote %v; expected %v

What it means

Returned by the GOCACHEPROG client when forwarding a cache entry body to the external cache subprocess: io.Copy from req.Body wrote fewer bytes than req.BodySize promised. The JSON-line protocol frames the body as a base64 string whose length must equal BodySize, so a short copy means the body stream ended early — either the body reader was mis-sized or the framing is inconsistent.

Source

Thrown at src/cmd/go/internal/cache/prog.go:247

		return err
	}
	if err := c.bw.WriteByte('\n'); err != nil {
		return err
	}
	if req.Body != nil && req.BodySize > 0 {
		if err := c.bw.WriteByte('"'); err != nil {
			return err
		}
		e := base64.NewEncoder(base64.StdEncoding, c.bw)
		wrote, err := io.Copy(e, req.Body)
		if err != nil {
			return err
		}
		if err := e.Close(); err != nil {
			return err
		}
		if wrote != req.BodySize {
			return fmt.Errorf("short write writing body to GOCACHEPROG for action %x, output %x: wrote %v; expected %v",
				req.ActionID, req.OutputID, wrote, req.BodySize)
		}
		if _, err := c.bw.WriteString("\"\n"); err != nil {
			return err
		}
	}
	if err := c.bw.Flush(); err != nil {
		return err
	}
	return nil
}

func (c *ProgCache) Get(a ActionID) (Entry, error) {
	if !c.can[cacheprog.CmdGet] {
		// They can't do a "get". Maybe they're a write-only cache.
		//
		// TODO(bradfitz,bcmills): figure out the proper error type here. Maybe
		// errors.ErrUnsupported? Is entryNotFoundError even appropriate? There

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify req.Body length matches req.BodySize at the call site before invoking Put.
  2. Update or replace the GOCACHEPROG helper; check its logs for early exit.
  3. Temporarily unset GOCACHEPROG to fall back to the built-in disk cache and confirm the helper is the cause.
  4. Ensure the helper does not close stdin before consuming the full body.

Example fix

// before: body and size disagree
req := &cache.PutReq{Body: someReader, BodySize: 1024}

// after: derive size from the same source
req := &cache.PutReq{Body: bytes.NewReader(buf), BodySize: int64(len(buf))}
Defensive patterns

Strategy: validation

Validate before calling

// ensure body size matches the reader before calling Put
if _, ok := body.(io.ReadSeeker); ok {
    n, _ := io.Copy(io.Discard, body.(io.ReadSeeker).Clone())
    body.(io.ReadSeeker).Seek(0, io.SeekStart)
    if n != req.BodySize { return fmt.Errorf("size mismatch") }
}

Try / catch

if err := c.Put(ctx, req); err != nil && strings.Contains(err.Error(), "short write") {
    // helper closed early; log and retry, or disable GOCACHEPROG
    os.Unsetenv("GOCACHEPROG")
}

Prevention

When it happens

Trigger: GOCACHEPROG is set to a helper program; Put is called with req.Body whose actual length != req.BodySize; the Body reader returns EOF early; a buggy GOCACHEPROG closes its stdin mid-stream.

Common situations: Third-party or custom GOCACHEPROG with a size-accounting bug; pipe/SIGPIPE issues when the helper exits early; racing teardown of the helper process.

Related errors


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