grpc/grpc-go · error

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

Error message

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

What it means

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.

Solutions

  1. Provide stale_age as a valid duration smaller than max_age, e.g. {"seconds": 240}, or omit it.
  2. Ensure the producer uses durationpb.New; validate that 0 < staleAge <= maxAge semantically after fixing the encoding.
  3. Remember the client caps stale_age at 5 minutes and clamps it to max_age if larger.

Example fix

// before
"staleAge": { "seconds": 0, "nanos": 5000000000 }

// after
"staleAge": { "seconds": 240 }
Defensive patterns

Strategy: validation

Validate before calling

func validateStaleAge(d *durationpb.Duration) error {
    if d == nil {
        return nil // zero is valid
    }
    if d.CheckValid() != nil {
        return fmt.Errorf("stale_age is invalid: %w", d.CheckValid())
    }
    return nil
}

Type guard

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

Prevention

When it happens

Trigger: stale_age seconds/nanos out of durationpb's representable range.

Common situations: Same as max_age: producer writing milliseconds into seconds, or hand-built nanos out of [0, 1e9).

Understand the failure class

Related errors


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

Appendix: 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 = maxAge

View on GitHub (pinned to 0c51461d27)