grpc/grpc-go · error

rls: failed to parse max_age in route lookup config %+v

Error message

rls: failed to parse max_age in route lookup config %+v: %v

What it means

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).

Solutions

  1. Provide max_age as a valid duration, e.g. {"seconds": 300}, or omit it to use the 5-minute default.
  2. Fix the producer to use durationpb.New(time.Duration) rather than hand-building fields.
  3. If you need longer caching, note the client caps max_age at 5 minutes (maxMaxAge), so set stale_age instead for background refresh.

Example fix

// before
"maxAge": { "seconds": 3153600000, "nanos": -5 }

// after
"maxAge": { "seconds": 300 }
Defensive patterns

Strategy: validation

Validate before calling

func validateMaxAge(d *durationpb.Duration) error {
    if d == nil {
        return nil // zero is valid; defaults to 5m
    }
    if d.CheckValid() != nil {
        return fmt.Errorf("max_age is invalid: %w", d.CheckValid())
    }
    return nil
}

Type guard

func isValidMaxAge(d *durationpb.Duration) bool {
    return d == nil || d.CheckValid() == nil
}

Prevention

When it happens

Trigger: max_age seconds/nanos out of durationpb's representable range. Zero is valid (means default), so this fires only for genuinely malformed durations.

Common situations: Control plane serializing milliseconds into the seconds field overflowing the range; malformed duration produced by a non-proto serializer.

Understand the failure class

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/a6bd2e4f62b0ff2a. Report an issue: GitHub.

Appendix: source

Thrown at balancer/rls/config.go:229

	}

	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 {
		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
	}

View on GitHub (pinned to 0c51461d27)