{"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/03255a9237b6eb32710f6bc4f2de9a675b99fe36/balancer/ringhash/picker.go#L43-L79","documentation":"Returned at RPC time by ringhash picker.Pick (picker.go:55-62) when requestHashHeader is empty AND no xDS-provided request hash is present in the RPC context. When requestHashHeader is empty, ringhash expects xDS (via a config selector on the channel) to inject the hash through iringhash.XDSRequestHash; if that returns ok==false the pick cannot place the RPC deterministically on the ring and fails.","triggerScenarios":"Using the ring_hash policy directly (not through xDS) without setting requestHashHeader. The RPC was made on a channel whose xDS config selector did not set the request hash (e.g. a direct dial that bypasses xDS, or an xDS update missing the hash-on field).","commonSituations":"Manually configuring ring_hash via service config on a non-xDS channel and forgetting requestHashHeader. xDS route configuration lacking a hash_policy so the selector never injects a hash.","solutions":["If you are not using xDS, set requestHashHeader in the ring_hash config to a header you attach to each RPC, so the picker hashes that header instead of requiring xDS.","If using xDS, ensure the route has a hash_policy (e.g. header/hash-all/request_path) so the config selector sets the request hash.","If you intentionally want random placement, note ringhash is the wrong policy; use round_robin instead."],"exampleFix":"// before: non-xDS channel, ring_hash with empty requestHashHeader\nraw := `{\"minRingSize\":1024,\"maxRingSize\":4096}` // every Pick() fails with this error\n\n// after: provide an explicit hash header (A76)\nraw := `{\"minRingSize\":1024,\"maxRingSize\":4096,\"requestHashHeader\":\"user-id\"}`\n// and attach the header on each RPC:\nctx := metadata.AppendToOutgoingContext(ctx, \"user-id\", strconv.Itoa(uid))","handlingStrategy":"fallback","validationCode":"// Ensure ring_hash is usable without xDS by always setting a hash header.\nfunc ensureRingHashHeader(cfg map[string]any) {\n    if _, ok := cfg[\"requestHashHeader\"]; !ok || cfg[\"requestHashHeader\"] == \"\" {\n        cfg[\"requestHashHeader\"] = \"x-request-hash\" // your app must set this header\n    }\n}","typeGuard":"func ringHashHasHashSource(cfg map[string]any, xdsProvidesHash bool) bool {\n    h, _ := cfg[\"requestHashHeader\"].(string)\n    return h != \"\" || xdsProvidesHash\n}","tryCatchPattern":"// This fails at Pick() time, not as a thrown exception. Prevent by config;\n// at call time you can still set the metadata header:\nctx := metadata.AppendToOutgoingContext(ctx, \"user-id\", uid)","preventionTips":["If not using xDS, always set requestHashHeader in the ring_hash config.","Ensure every RPC attaches that metadata key.","If using xDS, confirm routes define a hash_policy.","Prefer round_robin if you don't need affinity hashing."],"tags":["go","grpc","load-balancing","ring-hash","runtime","xds"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}