{"record":{"id":"a43b310396e02c2d","repo":"grpc/grpc-go","slug":"xds-connection-closed-or-handshakeinfo-dead","errorCode":null,"errorMessage":"xds: connection closed or HandshakeInfo dead","messagePattern":"xds: connection closed or HandshakeInfo dead","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/credentials/xds/handshake_info.go","lineNumber":136,"sourceCode":"\t\thi.rootProvider.Close()\n\t}\n\tif hi.identityProvider != nil {\n\t\thi.identityProvider.Close()\n\t}\n}\n\n// ClientSideTLSConfig loads the HandshakeInfo from hiPtr, marks it as in-use,\n// and returns the tls.Config along with a done callback that MUST be invoked\n// when the handshake completes. If no HandshakeInfo is stored in hiPtr or if\n// fallback credentials should be used, useFallback returns true.\nfunc ClientSideTLSConfig(ctx context.Context, hiPtr *atomic.Pointer[grpcsync.RefCounted[HandshakeInfo]], hostname string) (cfg *tls.Config, useFallback bool, done func(), err error) {\n\tif hiPtr == nil {\n\t\treturn nil, true, func() {}, nil\n\t}\n\tfor {\n\t\thiRC := hiPtr.Load()\n\t\tif hiRC == nil {\n\t\t\treturn nil, false, func() {}, errors.New(\"xds: connection closed or HandshakeInfo dead\")\n\t\t}\n\t\tif !hiRC.TryIncrement() {\n\t\t\tif hiPtr.Load() != hiRC {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, false, func() {}, errors.New(\"xds: connection closed or HandshakeInfo dead\")\n\t\t}\n\n\t\thi := hiRC.Value()\n\t\tif hi == nil || hi.UseFallbackCreds() {\n\t\t\thiRC.Decrement()\n\t\t\treturn nil, true, func() {}, nil\n\t\t}\n\t\tcfg, err := hi.clientSideTLSConfigInternal(ctx, hostname)\n\t\tif err != nil {\n\t\t\thiRC.Decrement()\n\t\t\treturn nil, false, func() {}, err\n\t\t}","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/credentials/xds/handshake_info.go#L118-L154","documentation":"Returned by ClientSideTLSConfig when the atomic pointer holding the reference-counted HandshakeInfo loads as nil. This means the xDS security configuration for the cluster was torn down or replaced before the TLS handshake could read it, so there is no live security config to use. The client cannot build a tls.Config and the handshake aborts. It is an internal xDS-credentials error surfaced during connection setup or reconfiguration.","triggerScenarios":"Calling ClientSideTLSConfig (via grpc.Dial with xds credentials) when the HandshakeInfo atomic pointer has been cleared—i.e., during xDS cluster/security-config removal, connection close, or a config update that swaps the pointer to nil. Occurs in the for-loop at handshake_info.go:134-136 when hiPtr.Load() returns nil on the first iteration.","commonSituations":"xDS management server pushes a security policy removal while RPCs are in-flight; cluster teardown during graceful shutdown; control plane (Istio/Envoy/xDS server) misconfiguration that drops the CertificateValidation/DownstreamTLSContext mid-session; version mismatch between grpc-go and the xDS server where security config fields are unrecognized and silently dropped.","solutions":["Check the xDS management server logs and LDS/CDS resources to confirm the cluster still has a valid security configuration (DownstreamTLSContext with certificate validation context).","Ensure the xDS control plane is not sending incomplete or empty security policy updates that clear the HandshakeInfo.","Verify the grpc-go client version is compatible with the xDS server version so security config fields are parsed correctly.","Use fallback credentials (the function returns useFallback=true when HandshakeInfo is genuinely absent) so non-xDS TLS paths work while xDS converges."],"exampleFix":"// The error is internal; ensure xDS config has valid security policy.\n// In your xDS management server, provide a DownstreamTLSContext:\n// before (misconfigured): no security policy on cluster\n// after:\n{\n  \"validation_context\": {\n    \"trusted_ca\": { \"filename\": \"/etc/ssl/certs/ca-cert.pem\" }\n  }\n}","handlingStrategy":"retry","validationCode":"// Before relying on xDS TLS, verify the channel is READY and xDS has converged.\n// There is no pre-call validation for internal HandshakeInfo state; use connection state.\nconn, err := grpc.DialContext(ctx, target,\n    grpc.WithBlock(),\n    grpc.WithReturnConnectionError(),\n)\nif err != nil { log.Fatal(err) }","typeGuard":null,"tryCatchPattern":"// In Go, check the error from RPC calls; gRPC will fail RPCs when xDS security config is missing.\nerr := client.Call(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"connection closed or HandshakeInfo dead\") {\n        // xDS reconfiguration race; reconnect or back off and retry\n        backoff.Retry(ctx, retryCall)\n    }\n}","preventionTips":["Monitor xDS resource updates and alert on security policy removals.","Keep grpc-go and xDS server versions aligned to avoid config field misinterpretation.","Use stable xDS security configurations to avoid frequent HandshakeInfo replacement.","Implement reconnection backoff so transient xDS races self-heal."],"tags":["xds","tls","credentials","security-config","grpc"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}