grpc/grpc-go · error
rls: failed to parse staleAge in route lookup config %+v: %v
Error message
rls: failed to parse staleAge in route lookup config %+v: %v
What it means
Returned by parseRLSProto (config.go:238-241) when convertDuration fails on RouteLookupConfig.stale_age, i.e. the stale_age Duration proto is structurally invalid (durationpb.CheckValid rejects out-of-range seconds/nanos). The policy also enforces that if stale_age is set then max_age must be set, and caps stale_age at min(max_age, 5m), but those are separate checks; this one is purely the Duration's structural validity.
Source
Thrown at balancer/rls/config.go:240
// - 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 {
maxAgeSet = true
}
staleAgeSet := false
staleAge, err := convertDuration(rlsProto.GetStaleAge())
if err != nil {
return nil, fmt.Errorf("rls: failed to parse staleAge in route lookup config %+v: %v", rlsProto, err)
}
if staleAge == 0 {
staleAge = maxMaxAge
} else {
staleAgeSet = true
}
if staleAgeSet && !maxAgeSet {
return nil, fmt.Errorf("rls: stale_age is set, but max_age is not in route lookup config %+v", rlsProto)
}
if staleAge > maxMaxAge {
staleAge = maxMaxAge
}
if !staleAgeSet && maxAge > maxMaxAge {
maxAge = maxMaxAge
}
if staleAge > maxAge {
staleAge = maxAgeView on GitHub (pinned to 03255a9237)
Solutions
- Set stale_age to a valid in-range duration (typically <= max_age and <= 5m).
- Build it with durationpb.New(time.Duration).
- Remember to also set max_age when you set stale_age.
Example fix
// before
rlc.StaleAge = &durationpb.Duration{ Seconds: 99999999999, Nanos: 0 } // out of range
// after
rlc.MaxAge = durationpb.New(5 * time.Minute)
rlc.StaleAge = durationpb.New(1 * time.Minute) Defensive patterns
Strategy: validation
Validate before calling
func validateStaleAge(staleAge, maxAge *durationpb.Duration) error {
if staleAge == nil { return nil }
if err := staleAge.CheckValid(); err != nil { return err }
if maxAge == nil || (maxAge.AsDuration() == 0 && maxAge.Seconds == 0 && maxAge.Nanos == 0) {
// will be caught later, but warn early
}
return nil
} Prevention
- Build stale_age with durationpb.New and keep it <= max_age.
- Always set max_age whenever you set stale_age (else the follow-on error fires).
- Both are capped at 5 minutes.
When it happens
Trigger: stale_age Duration proto has out-of-range seconds or nanos. A value of 0 is valid (defaults to 5m).
Common situations: Same as max_age: control-plane overflow or hand-crafted invalid Duration. Note: if you set stale_age you must also set max_age or you'll get the follow-on "stale_age is set, but max_age is not" error at config.go:248-250.
Understand the failure class
- 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 lookup_service_timeout in route lookup
- rls: failed to parse max_age 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/8436654fa36d9498.
Report an issue: GitHub.