grpc/grpc-go · error
ringhash: expected xDS config selector to set the request ha
Error message
ringhash: expected xDS config selector to set the request hash
What it means
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.
Source
Thrown at balancer/ringhash/picker.go:61
// requestHashHeader is the header key to look for the request hash. If it's
// empty, the request hash is expected to be set in the context via xDS.
// See gRFC A76.
requestHashHeader string
// hasEndpointInConnectingState is true if any of the endpoints is in
// CONNECTING.
hasEndpointInConnectingState bool
randUint64 func() uint64
}
func (p *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
usingRandomHash := false
var requestHash uint64
if p.requestHashHeader == "" {
var ok bool
if requestHash, ok = iringhash.XDSRequestHash(info.Ctx); !ok {
return balancer.PickResult{}, fmt.Errorf("ringhash: expected xDS config selector to set the request hash")
}
} else {
md, ok := metadata.FromOutgoingContext(info.Ctx)
if !ok || len(md.Get(p.requestHashHeader)) == 0 {
requestHash = p.randUint64()
usingRandomHash = true
} else {
values := strings.Join(md.Get(p.requestHashHeader), ",")
requestHash = xxhash.Sum64String(values)
}
}
e := p.ring.pick(requestHash)
ringSize := len(p.ring.items)
if !usingRandomHash {
// Per gRFC A61, because of sticky-TF with PickFirst's auto reconnect on TF,
// we ignore all TF subchannels and find the first ring entry in READY,
// CONNECTING or IDLE. If that entry is in IDLE, we need to initiate aView on GitHub (pinned to 03255a9237)
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.
Example fix
// before: non-xDS channel, ring_hash with empty requestHashHeader
raw := `{"minRingSize":1024,"maxRingSize":4096}` // every Pick() fails with this error
// after: provide an explicit hash header (A76)
raw := `{"minRingSize":1024,"maxRingSize":4096,"requestHashHeader":"user-id"}`
// and attach the header on each RPC:
ctx := metadata.AppendToOutgoingContext(ctx, "user-id", strconv.Itoa(uid)) Defensive patterns
Strategy: fallback
Validate before calling
// Ensure ring_hash is usable without xDS by always setting a hash header.
func ensureRingHashHeader(cfg map[string]any) {
if _, ok := cfg["requestHashHeader"]; !ok || cfg["requestHashHeader"] == "" {
cfg["requestHashHeader"] = "x-request-hash" // your app must set this header
}
} Type guard
func ringHashHasHashSource(cfg map[string]any, xdsProvidesHash bool) bool {
h, _ := cfg["requestHashHeader"].(string)
return h != "" || xdsProvidesHash
} Try / catch
// This fails at Pick() time, not as a thrown exception. Prevent by config; // at call time you can still set the metadata header: ctx := metadata.AppendToOutgoingContext(ctx, "user-id", uid)
Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- min_ring_size value of %d is greater than max supported valu
- max_ring_size value of %d is greater than max supported valu
- min %v is greater than max %v
- invalid requestHashHeader %q: %v
- invalid requestHashHeader %q, key must not end with "-bin"
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/78c3f3d29bb836c9.
Report an issue: GitHub.