grpc/grpc-go · error

failed to send ALTS handshaker request

Error message

failed to send ALTS handshaker request: %w

What it means

Returned by accessHandshakerService when sending a HandshakerReq on the open DoHandshake stream fails (h.stream.Send returns an error). The underlying error is wrapped with %w so it can be unwrapped/errors.Is-checked. This is a mid-handshake transport failure on the control stream to the GCP handshaker service.

Solutions

  1. Retry the ALTS connection — these are typically transient stream breaks.
  2. Ensure the context used for the handshake has an adequate deadline (server-side default is 30s).
  3. Check network connectivity and handshaker-service health; verify GRPC_ALTS_MAX_CONCURRENT_HANDSHAKES is not exhausted.
  4. Inspect the wrapped error (errors.Unwrap / errors.Is io.EOF, context.DeadlineExceeded) to distinguish cancellation from a genuine transport error.
Defensive patterns

Strategy: retry

Try / catch

// Stream Send failures during handshake are transient; retry the ALTS connection.
if err != nil {
    if errors.Is(err, io.EOF) || errors.Is(err, context.DeadlineExceeded) {
        // transient — retry with backoff
    }
    if strings.Contains(err.Error(), "failed to send ALTS handshaker request") {
        return fmt.Errorf("ALTS handshaker stream broken: %w", err)
    }
}

Prevention

When it happens

Trigger: After the DoHandshake stream is established (so not 191/192), a subsequent Send of a ClientStart/ServerStart/Next request fails. Reached on every accessHandshakerService call during doHandshake and processUntilDone loops.

Common situations: The metadata-server handshaker service closed/reset the stream mid-handshake; network interruption to metadata.google.internal.:8080; context cancellation/deadline (e.g. the 30s server timeout) cancelling the stream; the peer aborted; gRPC keepalive closing an idle handshaker stream.

Understand the failure class

Related errors


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

Appendix: source

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

	// on the returned record protocol.
	keyLen, ok := keyLength[result.RecordProtocol]
	if !ok {
		return nil, nil, fmt.Errorf("unknown resulted record protocol %v", result.RecordProtocol)
	}
	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 {

View on GitHub (pinned to 0c51461d27)