{"record":{"id":"78c3f3d29bb836c9","repo":"grpc/grpc-go","slug":"ringhash-expected-xds-config-selector-to-set-the","errorCode":null,"errorMessage":"ringhash: expected xDS config selector to set the request hash","messagePattern":"ringhash: expected xDS config selector to set the request hash","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/ringhash/picker.go","lineNumber":61,"sourceCode":"\t// requestHashHeader is the header key to look for the request hash. If it's\n\t// empty, the request hash is expected to be set in the context via xDS.\n\t// See gRFC A76.\n\trequestHashHeader string\n\n\t// hasEndpointInConnectingState is true if any of the endpoints is in\n\t// CONNECTING.\n\thasEndpointInConnectingState bool\n\n\trandUint64 func() uint64\n}\n\nfunc (p *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {\n\tusingRandomHash := false\n\tvar requestHash uint64\n\tif p.requestHashHeader == \"\" {\n\t\tvar ok bool\n\t\tif requestHash, ok = iringhash.XDSRequestHash(info.Ctx); !ok {\n\t\t\treturn balancer.PickResult{}, fmt.Errorf(\"ringhash: expected xDS config selector to set the request hash\")\n\t\t}\n\t} else {\n\t\tmd, ok := metadata.FromOutgoingContext(info.Ctx)\n\t\tif !ok || len(md.Get(p.requestHashHeader)) == 0 {\n\t\t\trequestHash = p.randUint64()\n\t\t\tusingRandomHash = true\n\t\t} else {\n\t\t\tvalues := strings.Join(md.Get(p.requestHashHeader), \",\")\n\t\t\trequestHash = xxhash.Sum64String(values)\n\t\t}\n\t}\n\n\te := p.ring.pick(requestHash)\n\tringSize := len(p.ring.items)\n\tif !usingRandomHash {\n\t\t// Per gRFC A61, because of sticky-TF with PickFirst's auto reconnect on TF,\n\t\t// we ignore all TF subchannels and find the first ring entry in READY,\n\t\t// CONNECTING or IDLE.  If that entry is in IDLE, we need to initiate a","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/ringhash/picker.go#L43-L79","documentation":"Returned by the ringhash picker's Pick() (balancer/ringhash/picker.go:61) when requestHashHeader is empty (xDS-hash mode) but iringhash.XDSRequestHash(ctx) returns ok == false. In xDS mode, the request hash is expected to be injected into the RPC context by the xDS config selector so that the same client consistently picks the same endpoint. Its absence means the xDS pipeline did not set the hash.","triggerScenarios":"The picker was built with requestHashHeader == \"\" (line 58), and at Pick time iringhash.XDSRequestHash(info.Ctx) returns (0, false) — no hash in the context. This happens when ring_hash is used through xDS but the config selector that normally sets the hash is absent or bypassed.","commonSituations":"Using ring_hash outside of a proper xDS configuration; the xDS RDS/RouteConfiguration lacks a hash policy; a custom interceptor that replaces the context drops the hash attribute; calling a ring_hash-backed channel directly without the xDS selector.","solutions":["Use ring_hash only within a full xDS-managed channel (xds.NewClientConn) so the config selector sets the hash.","Alternatively, set requestHashHeader in the config so the picker hashes an outgoing metadata value instead of requiring xDS injection.","Avoid interceptors that replace the RPC context without preserving the xDS hash."],"exampleFix":"// before: plain grpc client with ring_hash and no requestHashHeader → xDS hash missing\nconn, _ := grpc.NewClient(\"dns:///svc:50051\", grpc.WithDefaultServiceConfig(`{\"loadBalancingConfig\":[{\"ring_hash_experimental\":{}}]}`))\n\n// after: use an xDS-managed channel that injects the request hash, OR set requestHashHeader\nconn, _ := xds.NewClientConn(\"xds:///svc\", xds.WithServiceConfig(...))\n// or, for non-xDS, provide a header-based hash:\ngrpc.WithDefaultServiceConfig(`{\"loadBalancingConfig\":[{\"ring_hash_experimental\":{\"requestHashHeader\":\"user-id\"}}]}`)","handlingStrategy":"validation","validationCode":"// Prefer a header-based hash in non-xDS setups to avoid the missing\n// xDS-hash problem entirely.\nfunc safeRingHashConfig() string {\n    return `{\"loadBalancingConfig\":[{\"ring_hash_experimental\":{\"requestHashHeader\":\"user-id\"}}]}`\n}","typeGuard":null,"tryCatchPattern":"// If you cannot guarantee the xDS hash, catch the Unavailable and\n// fall back to a header-based hash or a different policy.\nresp, err := c.Call(ctx, in)\nif err != nil && strings.Contains(err.Error(), \"expected xDS config selector\") {\n    // reconfigure channel with requestHashHeader or switch policy\n}\nreturn resp, err","preventionTips":["Use ring_hash only through an xDS-managed channel (xds.NewClientConn).","Set requestHashHeader when running ring_hash without xDS so the picker hashes outgoing metadata.","Do not write interceptors that discard the incoming context (and its xDS hash attribute)."],"tags":["grpc","balancer","ringhash","xds","request-hash","picker","runtime"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}