{"record":{"id":"8436654fa36d9498","repo":"grpc/grpc-go","slug":"rls-failed-to-parse-staleage-in-route-lookup-conf","errorCode":null,"errorMessage":"rls: failed to parse staleAge in route lookup config %+v: %v","messagePattern":"rls: failed to parse staleAge in route lookup config %\\+v: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/rls/config.go","lineNumber":240,"sourceCode":"\t// - if `max_age` > 5m, it should be set to 5 minutes\n\t//   only if stale age is not set\n\t// - if `stale_age` > `max_age`, ignore it\n\t// - if `stale_age` is set, then `max_age` must also be set\n\tmaxAgeSet := false\n\tmaxAge, err := convertDuration(rlsProto.GetMaxAge())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"rls: failed to parse max_age in route lookup config %+v: %v\", rlsProto, err)\n\t}\n\tif maxAge == 0 {\n\t\tmaxAge = maxMaxAge\n\t} else {\n\t\tmaxAgeSet = true\n\t}\n\n\tstaleAgeSet := false\n\tstaleAge, err := convertDuration(rlsProto.GetStaleAge())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"rls: failed to parse staleAge in route lookup config %+v: %v\", rlsProto, err)\n\t}\n\tif staleAge == 0 {\n\t\tstaleAge = maxMaxAge\n\t} else {\n\t\tstaleAgeSet = true\n\t}\n\n\tif staleAgeSet && !maxAgeSet {\n\t\treturn nil, fmt.Errorf(\"rls: stale_age is set, but max_age is not in route lookup config %+v\", rlsProto)\n\t}\n\tif staleAge > maxMaxAge {\n\t\tstaleAge = maxMaxAge\n\t}\n\tif !staleAgeSet && maxAge > maxMaxAge {\n\t\tmaxAge = maxMaxAge\n\t}\n\tif staleAge > maxAge {\n\t\tstaleAge = maxAge","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/rls/config.go#L222-L258","documentation":"Emitted from parseRLSProto (config.go:240) when convertDuration fails for RouteLookupConfig.stale_age. stale_age is the age after which a cached entry is refreshed in the background while still serving stale data; zero is valid (defaults to maxMaxAge). Only an out-of-range durationpb value triggers this.","triggerScenarios":"stale_age seconds/nanos out of durationpb's representable range.","commonSituations":"Same as max_age: producer writing milliseconds into seconds, or hand-built nanos out of [0, 1e9).","solutions":["Provide stale_age as a valid duration smaller than max_age, e.g. {\"seconds\": 240}, or omit it.","Ensure the producer uses durationpb.New; validate that 0 < staleAge <= maxAge semantically after fixing the encoding.","Remember the client caps stale_age at 5 minutes and clamps it to max_age if larger."],"exampleFix":"// before\n\"staleAge\": { \"seconds\": 0, \"nanos\": 5000000000 }\n\n// after\n\"staleAge\": { \"seconds\": 240 }","handlingStrategy":"validation","validationCode":"func validateStaleAge(d *durationpb.Duration) error {\n    if d == nil {\n        return nil // zero is valid\n    }\n    if d.CheckValid() != nil {\n        return fmt.Errorf(\"stale_age is invalid: %w\", d.CheckValid())\n    }\n    return nil\n}","typeGuard":"func isValidStaleAge(d *durationpb.Duration) bool {\n    return d == nil || d.CheckValid() == nil\n}","tryCatchPattern":null,"preventionTips":["Always set max_age when you set stale_age, with stale_age <= max_age.","Use durationpb.New to avoid nanos/seconds range errors.","Note the client clamps stale_age to max_age and to the 5-minute cap."],"tags":["rls","duration","validation","proto"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}