grpc/grpc-go · error

failed to receive ALTS handshaker response

Error message

failed to receive ALTS handshaker response: %w

What it means

Returned by accessHandshakerService when receiving a HandshakerResp on the open DoHandshake stream fails (h.stream.Recv returns an error). The underlying error is wrapped with %w. This indicates the control stream to the GCP handshaker service broke after a request was sent.

Solutions

  1. Retry the ALTS handshake/connection — transient.
  2. Inspect the wrapped error: io.EOF often means the handshaker service closed the stream; context.Canceled/DeadlineExceeded means the caller's deadline was too short.
  3. Verify metadata-server reachability and health; check GCP status.
  4. Increase handshake deadlines if large handshakes are timing out.
Defensive patterns

Strategy: retry

Try / catch

// Stream Recv failures during handshake are typically transient; retry.
if err != nil {
    if errors.Is(err, io.EOF) {
        // handshaker service closed the stream; retry the handshake
    }
    if errors.Is(err, context.DeadlineExceeded) {
        // increase handshake deadline
    }
}

Prevention

When it happens

Trigger: After successfully Sending a HandshakerReq, Recv() on the same stream returns an error (EOF, transport error, RST_STREAM). Reached during every doHandshake iteration and the processUntilDone loop.

Common situations: Handshaker service crashed/restarted mid-handshake; network drop to the metadata server; the stream was cancelled by context timeout; the peer sent no more frames causing processUntilDone to loop and eventually fail Recv; RPC-level error from the handshaker service causing the stream to terminate.

Understand the failure class

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/e05e48a7ef5d4fee. Report an issue: GitHub.

Appendix: source

Thrown at credentials/alts/internal/handshaker/handshaker.go:309

	}
	maxFrameSize := int(envconfig.ALTSMaxFrameSize)
	if peerMax := int(result.GetMaxFrameSize()); peerMax > 0 {
		maxFrameSize = min(peerMax, maxFrameSize)
	}
	sc, err := conn.NewConnWithMaxFrameSize(h.conn, h.side, result.GetRecordProtocol(), result.KeyData[:keyLen], extra, maxFrameSize)
	if err != nil {
		return nil, nil, err
	}
	return sc, result, nil
}

func (h *altsHandshaker) accessHandshakerService(req *altspb.HandshakerReq) (*altspb.HandshakerResp, error) {
	if err := h.stream.Send(req); err != nil {
		return nil, fmt.Errorf("failed to send ALTS handshaker request: %w", err)
	}
	resp, err := h.stream.Recv()
	if err != nil {
		return nil, fmt.Errorf("failed to receive ALTS handshaker response: %w", err)
	}
	return resp, nil
}

// processUntilDone processes the handshake until the handshaker service returns
// the results. Handshaker service takes care of frame parsing, so we read
// whatever received from the network and send it to the handshaker service.
func (h *altsHandshaker) processUntilDone(resp *altspb.HandshakerResp, extra []byte) (*altspb.HandshakerResult, []byte, error) {
	var lastWriteTime time.Time
	buf := make([]byte, frameLimit)
	for {
		if len(resp.OutFrames) > 0 {
			lastWriteTime = time.Now()
			if _, err := h.conn.Write(resp.OutFrames); err != nil {
				return nil, nil, err
			}
		}
		if resp.Result != nil {

View on GitHub (pinned to 0c51461d27)