grpc/grpc-go · error

lrs: unexpected message type %T

Error message

lrs: unexpected message type %T

What it means

Returned by recvFirstLoadStatsResponse when stream.Recv() succeeds but proto.Unmarshal of the bytes into envoy.service.load_stats.v3.LoadStatsResponse fails. This means the server sent bytes on the LRS stream that are not a valid LoadStatsResponse protobuf. The runner treats it as a stream failure and retries with backoff.

Source

Thrown at internal/xds/clients/lrsclient/lrs_stream.go:214

}

// recvFirstLoadStatsResponse receives the first LoadStatsResponse from the LRS
// server.  Returns the following:
//   - a list of cluster names requested by the server or an empty slice if the
//     server requested for load from all clusters
//   - the load reporting interval, and
//   - any error encountered
func (lrs *streamImpl) recvFirstLoadStatsResponse(stream clients.Stream) ([]string, time.Duration, error) {
	r, err := stream.Recv()
	if err != nil {
		return nil, 0, fmt.Errorf("lrs: failed to receive first LoadStatsResponse: %v", err)
	}
	var resp v3lrspb.LoadStatsResponse
	if err := proto.Unmarshal(r, &resp); err != nil {
		if lrs.logger.V(2) {
			lrs.logger.Infof("Failed to unmarshal response to LoadStatsResponse: %v", err)
		}
		return nil, time.Duration(0), fmt.Errorf("lrs: unexpected message type %T", r)
	}
	if lrs.logger.V(perRPCVerbosityLevel) {
		lrs.logger.Infof("Received first LoadStatsResponse: %s", pretty.ToJSON(&resp))
	}

	internal := resp.GetLoadReportingInterval()
	if internal.CheckValid() != nil {
		return nil, 0, fmt.Errorf("lrs: invalid load_reporting_interval: %v", err)
	}
	loadReportingInterval := internal.AsDuration()

	clusters := resp.Clusters
	if resp.SendAllClusters {
		// Return an empty slice to send stats for all clusters.
		clusters = []string{}
	}

	return clusters, loadReportingInterval, nil

View on GitHub (pinned to 03255a9237)

Solutions

  1. Confirm the remote is an xDS v3 management server that implements envoy.service.load_stats.v3.LoadReportingService.
  2. Capture the stream with grpcurl --protocol h2 against the LRS service path to see what the server actually returns.
  3. Check for an intermediating proxy/LB that may rewrite the body, and bypass it to test.
  4. Ensure client and server use the same go-control-plane / grpc-go proto versions for load_stats/v3.
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Connecting to an endpoint that speaks a different protocol on the StreamLoadStats path (e.g. an HTTP/1 error page, an ADS/LRS version mismatch, or a proxy injecting HTML), or a buggy/non-conformant LRS server that emits an unrelated protobuf on the stream.

Common situations: Pointing the LRS client at a v2 LRS server while using v3 protos, a load balancer/proxy returning a plaintext error body over the otherwise-HTTP/2 stream, or an Envoy/control-plane build that serializes a different message type for StreamLoadStats.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/f48d186522fd3bff. Report an issue: GitHub.