{"record":{"id":"a6bd2e4f62b0ff2a","repo":"grpc/grpc-go","slug":"rls-failed-to-parse-max-age-in-route-lookup-confi","errorCode":null,"errorMessage":"rls: failed to parse max_age in route lookup config %+v: %v","messagePattern":"rls: failed to parse max_age in route lookup config %\\+v: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/rls/config.go","lineNumber":229,"sourceCode":"\t}\n\n\tlookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"rls: failed to parse lookup_service_timeout in route lookup config %+v: %v\", rlsProto, err)\n\t}\n\tif lookupServiceTimeout == 0 {\n\t\tlookupServiceTimeout = defaultLookupServiceTimeout\n\t}\n\n\t// Validations performed here:\n\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","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/rls/config.go#L211-L247","documentation":"Emitted from parseRLSProto (config.go:229) when convertDuration fails for RouteLookupConfig.max_age (durationpb.CheckValid rejects out-of-range values). max_age is the upper bound on how long a cached RLS result is used; a zero/absent value is allowed and defaults to 5 minutes (and is capped at 5 minutes anyway).","triggerScenarios":"max_age seconds/nanos out of durationpb's representable range. Zero is valid (means default), so this fires only for genuinely malformed durations.","commonSituations":"Control plane serializing milliseconds into the seconds field overflowing the range; malformed duration produced by a non-proto serializer.","solutions":["Provide max_age as a valid duration, e.g. {\"seconds\": 300}, or omit it to use the 5-minute default.","Fix the producer to use durationpb.New(time.Duration) rather than hand-building fields.","If you need longer caching, note the client caps max_age at 5 minutes (maxMaxAge), so set stale_age instead for background refresh."],"exampleFix":"// before\n\"maxAge\": { \"seconds\": 3153600000, \"nanos\": -5 }\n\n// after\n\"maxAge\": { \"seconds\": 300 }","handlingStrategy":"validation","validationCode":"func validateMaxAge(d *durationpb.Duration) error {\n    if d == nil {\n        return nil // zero is valid; defaults to 5m\n    }\n    if d.CheckValid() != nil {\n        return fmt.Errorf(\"max_age is invalid: %w\", d.CheckValid())\n    }\n    return nil\n}","typeGuard":"func isValidMaxAge(d *durationpb.Duration) bool {\n    return d == nil || d.CheckValid() == nil\n}","tryCatchPattern":null,"preventionTips":["Serialize max_age via durationpb.New(time.Duration).","Remember the client caps effective max_age at 5 minutes regardless.","Validate durations at the producer boundary to catch overflow before it reaches clients."],"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"}