grpc/grpc-go · error
rls: failed to parse lookup_service_timeout in route lookup
Error message
rls: failed to parse lookup_service_timeout in route lookup config %+v: %v
What it means
Returned by parseRLSProto (config.go:213-216) when convertDuration fails on RouteLookupConfig.lookup_service_timeout. convertDuration calls durationpb.Duration.CheckValid(), which rejects out-of-range durations (seconds/nanos out of int64 range, nanos not in [-999999999,999999999]). So the proto Duration is structurally invalid. The %+v is the whole rlsProto.
Source
Thrown at balancer/rls/config.go:215
}
parsedTarget, err := url.Parse(lookupService)
if err != nil {
// url.Parse() fails if scheme is missing. Retry with default scheme.
parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
if err != nil {
return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
}
}
if parsedTarget.Scheme == "" {
parsedTarget.Scheme = resolver.GetDefaultScheme()
}
if resolver.Get(parsedTarget.Scheme) == nil {
return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
}
lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
if err != nil {
return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err)
}
if lookupServiceTimeout == 0 {
lookupServiceTimeout = defaultLookupServiceTimeout
}
// Validations performed here:
// - if `max_age` > 5m, it should be set to 5 minutes
// only if stale age is not set
// - if `stale_age` > `max_age`, ignore it
// - if `stale_age` is set, then `max_age` must also be set
maxAgeSet := false
maxAge, err := convertDuration(rlsProto.GetMaxAge())
if err != nil {
return nil, fmt.Errorf("rls: failed to parse max_age in route lookup config %+v: %v", rlsProto, err)
}
if maxAge == 0 {
maxAge = maxMaxAge
} else {View on GitHub (pinned to 03255a9237)
Solutions
- Set lookup_service_timeout to a normal, in-range value (e.g. 10s; the default if unset). Typical values are 1-10 seconds.
- If generating the Duration proto, use durationpb.New(time.Duration) which always produces a valid value.
- Verify the control plane's computation of the timeout field.
Example fix
// before: hand-built out-of-range duration
rlc.LookupServiceTimeout = &durationpb.Duration{ Seconds: math.MaxInt64, Nanos: 5000000000 }
// after
rlc.LookupServiceTimeout = durationpb.New(10 * time.Second) Defensive patterns
Strategy: validation
Validate before calling
import "google.golang.org/protobuf/types/known/durationpb"
func validateLookupServiceTimeout(d *durationpb.Duration) error {
if d == nil { return nil }
return d.CheckValid()
} Prevention
- Build durations with durationpb.New(time.Duration), never by hand.
- Use a small timeout (e.g. 10s); 0 falls back to the 10s default.
When it happens
Trigger: lookup_service_timeout has seconds beyond int64 range or malformed nanos, e.g. from a control plane that computed the duration incorrectly.
Common situations: Control plane arithmetic overflow when building the duration. Manually crafting a Duration proto with garbage values. Edge value of 0 is fine (falls back to 10s default); only out-of-range values trigger this.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- rls: failed to parse max_age in route lookup config %+v: %v
- rls: failed to parse staleAge in route lookup config %+v: %v
- rls: bad RouteLookupConfig proto %+v: %v
- rls: json unmarshal failed for service config %+v: %v
- rls: bad control channel service config %q: %v
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/44670102dbd81ccd.
Report an issue: GitHub.