{"record":{"id":"e05e48a7ef5d4fee","repo":"grpc/grpc-go","slug":"failed-to-receive-alts-handshaker-response-w","errorCode":null,"errorMessage":"failed to receive ALTS handshaker response: %w","messagePattern":"failed to receive ALTS handshaker response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/alts/internal/handshaker/handshaker.go","lineNumber":309,"sourceCode":"\t}\n\tmaxFrameSize := int(envconfig.ALTSMaxFrameSize)\n\tif peerMax := int(result.GetMaxFrameSize()); peerMax > 0 {\n\t\tmaxFrameSize = min(peerMax, maxFrameSize)\n\t}\n\tsc, err := conn.NewConnWithMaxFrameSize(h.conn, h.side, result.GetRecordProtocol(), result.KeyData[:keyLen], extra, maxFrameSize)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn sc, result, nil\n}\n\nfunc (h *altsHandshaker) accessHandshakerService(req *altspb.HandshakerReq) (*altspb.HandshakerResp, error) {\n\tif err := h.stream.Send(req); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to send ALTS handshaker request: %w\", err)\n\t}\n\tresp, err := h.stream.Recv()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to receive ALTS handshaker response: %w\", err)\n\t}\n\treturn resp, nil\n}\n\n// processUntilDone processes the handshake until the handshaker service returns\n// the results. Handshaker service takes care of frame parsing, so we read\n// whatever received from the network and send it to the handshaker service.\nfunc (h *altsHandshaker) processUntilDone(resp *altspb.HandshakerResp, extra []byte) (*altspb.HandshakerResult, []byte, error) {\n\tvar lastWriteTime time.Time\n\tbuf := make([]byte, frameLimit)\n\tfor {\n\t\tif len(resp.OutFrames) > 0 {\n\t\t\tlastWriteTime = time.Now()\n\t\t\tif _, err := h.conn.Write(resp.OutFrames); err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t}\n\t\tif resp.Result != nil {","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/alts/internal/handshaker/handshaker.go#L291-L327","documentation":"Returned by accessHandshakerService when h.stream.Recv() fails while waiting for the handshaker service's HandshakerResp. %w wraps the receive error. The stream is broken on the read side: the service closed it (io.EOF), sent a GOAWAY, the connection reset, or the context expired.","triggerScenarios":"Calling Recv on the DoHandshake stream after the handshaker service terminated it — service-side error followed by close, network interruption mid-handshake, context cancellation, or the metadata server process restarting. Always fires together with or shortly after [163] once the stream is torn down.","commonSituations":"Metadata server restarts on GKE upgrades, network policy killing idle streams, handshaker service OOM/crash, or handshake loops that keep retrying on a dead stream instead of re-dialing.","solutions":["Detect io.EOF / context errors via errors.Is on the wrapped error and tear down + re-create the handshaker rather than looping on the dead stream.","Ensure the ClientConn to the handshaker service reconnects automatically (default grpc behavior) and that handshakes are re-initiated on new connections.","Raise the handshake context timeout above the observed metadata-server round-trip latency.","Monitor the handshaker service health and metadata-server availability if this spikes."],"exampleFix":"// before: retry on a dead stream\nfor {\n    resp, err := h.accessHandshakerService(req)\n    if err != nil { continue }\n}\n\n// after: tear down and rebuild on receive failure\nresp, err := h.accessHandshakerService(req)\nif err != nil {\n    h.Close()\n    return nil, err // caller re-runs ClientHandshake on a fresh handshaker\n}","handlingStrategy":"retry","validationCode":"// Pre-check that the stream is still open before relying on Recv.\nfunc (h *altsHandshaker) streamAlive() bool { return h.stream != nil }","typeGuard":null,"tryCatchPattern":"resp, err := h.accessHandshakerService(req)\nif err != nil {\n    if errors.Is(err, io.EOF) {\n        // service closed the stream cleanly: rebuild handshaker.\n        h.Close()\n    }\n    if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n        return nil, err\n    }\n    return nil, fmt.Errorf(\"handshaker recv failed: %w\", err)\n}","preventionTips":["Re-create the handshaker (Close + NewClientHandshaker) once Recv fails.","Distinguish io.EOF (clean close) from network resets in error handling.","Do not busy-loop on a dead stream.","Keep the metadata-server reachable and stable to avoid mid-handshake teardowns."],"tags":["alts","grpc","network","stream","handshake"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}