nats-io/nats-server · error

short write on body (%d != %d)

Error message

short write on body (%d != %d)

What it means

This error is thrown by LocalCache.Compress in server/ocsp_responsecache.go when the s2 (Snappy) compression writer consumed fewer bytes than the full body length during io.CopyN. It means the OCSP response body was not fully written into the compression buffer, producing a truncated compressed record. The library throws it to prevent storing a silently corrupt cache entry.

Source

Thrown at server/ocsp_responsecache.go:352

		case ocsp.Good:
			c.stats.Goods++
		case ocsp.Revoked:
			c.stats.Revokes++
		case ocsp.Unknown:
			c.stats.Unknowns++
		}
	}
}

func (c *LocalCache) Compress(buf []byte) ([]byte, error) {
	bodyLen := int64(len(buf))
	var output bytes.Buffer
	writer := s2.NewWriter(&output)
	input := bytes.NewReader(buf[:bodyLen])
	if n, err := io.CopyN(writer, input, bodyLen); err != nil {
		return nil, fmt.Errorf(certidp.ErrCannotWriteCompressed, err)
	} else if n != bodyLen {
		return nil, fmt.Errorf(certidp.ErrTruncatedWrite, n, bodyLen)
	}
	if err := writer.Close(); err != nil {
		return nil, fmt.Errorf(certidp.ErrCannotCloseWriter, err)
	}
	return output.Bytes(), nil
}

func (c *LocalCache) Decompress(buf []byte) ([]byte, error) {
	bodyLen := int64(len(buf))
	input := bytes.NewReader(buf[:bodyLen])
	reader := io.NopCloser(s2.NewReader(input))
	output, err := io.ReadAll(reader)
	if err != nil {
		return nil, fmt.Errorf(certidp.ErrCannotReadCompressed, err)
	}
	return output, reader.Close()
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Log n and bodyLen and inspect the buffer passed to Put; ensure buf[:bodyLen] actually contains bodyLen bytes.
  2. Verify the upstream read that produced buf filled it completely before calling Put.
  3. Replace the short-write path with io.Copy into the writer and check the writer's error instead of assuming bodyLen.
  4. If persistent, bypass compression (store uncompressed) or regenerate the OCSP response and retry Put.

Example fix

// before
if n, err := io.CopyN(writer, input, bodyLen); err != nil {
	return nil, fmt.Errorf(certidp.ErrCannotWriteCompressed, err)
} else if n != bodyLen {
	return nil, fmt.Errorf(certidp.ErrTruncatedWrite, n, bodyLen)
}
// after
if n, err := io.CopyN(writer, input, bodyLen); err != nil {
	return nil, fmt.Errorf(certidp.ErrCannotWriteCompressed, err)
} else if n != bodyLen {
	// fall back to storing the raw response rather than a truncated record
	return buf, nil
}
Defensive patterns

Strategy: validation

Validate before calling

// before calling Put
if int64(len(buf)) < bodyLen {
	return fmt.Errorf("buffer too small: have %d, need %d", len(buf), bodyLen)
}

Type guard

func isCompleteBody(buf []byte, bodyLen int64) bool {
	return int64(len(buf)) >= bodyLen
}

Prevention

When it happens

Trigger: Calling LocalCache.Put on an OCSP response where the input reader (bytes.NewReader over buf[:bodyLen]) yields fewer bytes than bodyLen, or the s2 writer errors partway through the copy so io.CopyN returns n < bodyLen.

Common situations: A buffer whose declared bodyLen exceeds the data actually read from the OCSP responder response; a slice mismatch after partial reads; s2 writer returning a short write under memory pressure.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/0d8527d775b911d9. Report an issue: GitHub.