weaviate/weaviate · error

invalid packed digests length %d: not a multiple of %d

Error message

invalid packed digests length %d: not a multiple of %d

What it means

RepairDigestsFromBinary decodes a packed payload of fixed-size records (CompareDigestsRecordLength = 25 bytes: 16-byte UUID + 8-byte big-endian timestamp + 1 flag byte). If the total length is not an exact multiple of the record size, the payload is malformed — it cannot be split into whole records — and decoding is rejected before any parsing.

Source

Thrown at usecases/replica/repair_digests_codec.go:52

	out := make([]byte, 0, len(digests)*CompareDigestsRecordLength)
	var buf [CompareDigestsRecordLength]byte
	for _, d := range digests {
		copy(buf[:16], d.ID[:])
		binary.BigEndian.PutUint64(buf[16:24], uint64(d.UpdateTime))
		buf[24] = 0
		if d.Deleted {
			buf[24] = CompareDigestsFlagDeleted
		}
		out = append(out, buf[:]...)
	}
	return out
}

// RepairDigestsFromBinary decodes a RepairDigestsToBinary payload, rejecting
// any length that is not a whole number of records.
func RepairDigestsFromBinary(data []byte) ([]types.RepairDigest, error) {
	if len(data)%CompareDigestsRecordLength != 0 {
		return nil, fmt.Errorf("invalid packed digests length %d: not a multiple of %d",
			len(data), CompareDigestsRecordLength)
	}
	digests := make([]types.RepairDigest, len(data)/CompareDigestsRecordLength)
	for i := range digests {
		rec := data[i*CompareDigestsRecordLength : (i+1)*CompareDigestsRecordLength]
		id, err := uuid.FromBytes(rec[:16])
		if err != nil {
			return nil, fmt.Errorf("parse uuid from binary record: %w", err)
		}
		digests[i] = types.RepairDigest{
			ID:         id,
			UpdateTime: int64(binary.BigEndian.Uint64(rec[16:24])),
			Deleted:    rec[24]&CompareDigestsFlagDeleted != 0,
		}
	}
	return digests, nil
}

View on GitHub (pinned to 75aa4b6d11)

Solutions

  1. Verify the sender negotiated the packed encoding (RepairDigestsEncodingPacked) and did not fall back to the proto encoding — mismatched encodings change the byte layout.
  2. Log the payload length and compare against (expected record count) * 25 to spot truncation or trailing bytes.
  3. Retransmit the digest payload; a single corrupted transfer is the usual cause.
  4. If a peer persistently produces bad lengths, check its Weaviate version for codec bugs and align versions.

Example fix

// before: assume packed blindly | digests, err := replica.RepairDigestsFromBinary(body) | // after: guard on negotiated encoding first | if encoding == replica.RepairDigestsEncodingPacked { digests, err = replica.RepairDigestsFromBinary(body) } else { digests = decodeRepeatedProto(body) }
Defensive patterns

Strategy: validation

Validate before calling

const recordLen = 25; if len(payload)%recordLen != 0 { return fmt.Errorf("payload %d not multiple of %d — wrong encoding or truncated", len(payload), recordLen) }

Try / catch

digests, err := replica.RepairDigestsFromBinary(body); if err != nil { /* fall back to the repeated-proto decoding path or request retransmission */ return nil, fmt.Errorf("packed digests rejected: %w", err) }

Prevention

When it happens

Trigger: RepairDigestsFromBinary or decodePackedDigests receiving a payload whose byte length modulo 25 is nonzero: a peer sending the packed encoding with trailing bytes, truncated gRPC/REST transfer, an older peer sending the repeated-proto payload into the packed decoder, or compression/framing bytes left in the buffer.

Common situations: Version skew where one node claims RepairDigestsEncodingPacked but sends the legacy repeated-proto body; network truncation; a proxy or middleware altering the body; handcrafted test payloads.

Related errors


AI-assisted analysis of weaviate/weaviate@75aa4b6d11 (2026-09-04). Data as JSON: /api/errors/334d8bb2f2537c79. Report an issue: GitHub.