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

View on GitHub (pinned to 03255a9237)

Solutions

  1. Set stale_age to a valid in-range duration (typically <= max_age and <= 5m).
  2. Build it with durationpb.New(time.Duration).
  3. 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

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

Related errors


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