{"record":{"id":"9abc69b90e1b2e60","repo":"grpc/grpc-go","slug":"all-subconns-are-in-transientfailure","errorCode":null,"errorMessage":"all SubConns are in TransientFailure","messagePattern":"all SubConns are in TransientFailure","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/balancer.go","lineNumber":275,"sourceCode":"\tBytesReceived bool\n\t// ServerLoad is the load received from server. It's usually sent as part of\n\t// trailing metadata.\n\t//\n\t// The only supported type now is *orca_v3.LoadReport.\n\tServerLoad any\n}\n\nvar (\n\t// ErrNoSubConnAvailable indicates no SubConn is available for pick().\n\t// gRPC will block the RPC until a new picker is available via UpdateState().\n\tErrNoSubConnAvailable = errors.New(\"no SubConn is available\")\n\t// ErrTransientFailure indicates all SubConns are in TransientFailure.\n\t// WaitForReady RPCs will block, non-WaitForReady RPCs will fail.\n\t//\n\t// Deprecated: return an appropriate error based on the last resolution or\n\t// connection attempt instead.  The behavior is the same for any non-gRPC\n\t// status error.\n\tErrTransientFailure = errors.New(\"all SubConns are in TransientFailure\")\n)\n\n// PickResult contains information related to a connection chosen for an RPC.\ntype PickResult struct {\n\t// SubConn is the connection to use for this pick, if its state is Ready.\n\t// If the state is not Ready, gRPC will block the RPC until a new Picker is\n\t// provided by the balancer (using ClientConn.UpdateState).  The SubConn\n\t// must be one returned by ClientConn.NewSubConn.\n\tSubConn SubConn\n\n\t// Done is called when the RPC is completed.  If the SubConn is not ready,\n\t// this will be called with a nil parameter.  If the SubConn is not a valid\n\t// type, Done may not be called.  May be nil if the balancer does not wish\n\t// to be notified when the RPC completes.\n\tDone func(DoneInfo)\n\n\t// Metadata provides a way for LB policies to inject arbitrary per-call\n\t// metadata. Any metadata returned here will be merged with existing","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/balancer/balancer.go#L257-L293","documentation":"Deprecated sentinel (balancer.ErrTransientFailure) a Picker returns when every SubConn is in TransientFailure. Per balancer/balancer.go:269-275 and :330-332, WaitForReady RPCs block while non-WaitForReady RPCs are terminated with status Unavailable carrying this message. The doc comment explicitly tells balancer authors to instead return an appropriate error based on the last resolution/connection attempt; behavior for any non-gRPC-status error is identical, so the sentinel is redundant.","triggerScenarios":"A Picker returns balancer.ErrTransientFailure (or any non-status error with the same semantics) after the connectivity evaluator reports TransientFailure for all subchannels — i.e. every backend connection attempt failed (TCP refused, TLS handshake error, auth failure) and the LB policy aggregated to TransientFailure.","commonSituations":"All backends down or unreachable; TLS trust chain broken so every handshake fails; the wrong target/authority so connections are refused; a custom balancer still using the deprecated sentinel instead of the last connection error.","solutions":["Inspect cc.connectionError() / the last subchannel error to find the real cause (TLS, DNS, refused) — the TransientFailure message itself is not diagnostic.","Fix the root backend/network issue so at least one SubConn reaches READY.","If you maintain a Picker, stop returning ErrTransientFailure; wrap the most recent connection attempt error (e.g. status.Errorf(codes.Unavailable, '%v', lastErr)).","Use grpc.WaitForReady(true) with a deadline if you want RPCs to ride through transient outages."],"exampleFix":"// before (custom picker)\nreturn balancer.PickResult{}, balancer.ErrTransientFailure\n\n// after\nreturn balancer.PickResult{}, status.Errorf(codes.Unavailable, \"last conn error: %v\", lastConnErr)","handlingStrategy":"retry","validationCode":"// Treat TransientFailure as recoverable; check real cause\nif cc.GetState() == connectivity.TransientFailure {\n    if e := cc.connectionError(); e != nil { log.Println(\"root cause:\", e) }\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, balancer.ErrTransientFailure) || status.Code(err) == codes.Unavailable {\n    // back off and retry; consult connectionError() for the real cause\n}","preventionTips":["Do not return ErrTransientFailure from custom pickers — wrap the last connection error instead.","Enable retry policy in the service config for UNAVAILABLE.","Log cc.connectionError() so TransientFailure is diagnosable."],"tags":["balancer","transient-failure","deprecated","grpc-go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}