grpc/grpc-go · error

out of range

Error message

out of range: %q

What it means

Fires at duration.go:90 when the parsed seconds value exceeds the protobuf Duration spec maximum of 315,576,000,000 seconds (~10,000 years). This is a hard cap defined by the protobuf Duration type, independent of Go's time.Duration range; values above it are rejected even though they might fit in an int64.

Solutions

  1. Read the offending %q value and compare its seconds part against 315,576,000,000.
  2. Find the field (retry MaxBackoff, timeout, etc.) that produced the value and cap or correct it.
  3. If the number was meant to be a different unit, re-express it correctly (e.g. milliseconds -> seconds).
  4. Add a unit test that asserts your config-generation code never emits seconds above the protobuf limit.

Example fix

// before
// "maxBackoff": "1000000000000s"   // 1e12 s -> out of range

// after
// "maxBackoff": "600s"             // 10 minutes
Defensive patterns

Strategy: validation

Validate before calling

const maxProtoSeconds = 315_576_000_000

func assertDurationInRange(d time.Duration) error {
    if sec := int64(d / time.Second); sec > maxProtoSeconds {
        return fmt.Errorf("duration %s exceeds protobuf max %ds", d, maxProtoSeconds)
    }
    return nil
}

Try / catch

if err := json.Unmarshal(rawConfig, &cfg); err != nil {
    if strings.Contains(err.Error(), "out of range") {
        // a Duration field exceeds the ~10000-year protobuf cap
    }
    return err
}

Prevention

When it happens

Trigger: A Duration JSON string whose integer seconds part parses successfully but is greater than the const maxProtoSeconds (315576000000). Examples: "999999999999s", "315576000001s", or a backoff/timeout field populated from a calculation that produced a huge number.

Common situations: A retry/backoff multiplier computed from untrusted input that explodes into a huge value; config where MaxBackoff was meant to be in milliseconds but written as seconds ("1000000000000s"); copy-paste of a nanosecond quantity into a seconds field.

Related errors


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

Appendix: source

Thrown at internal/serviceconfig/duration.go:90

		s = s[1:]
	}
	ss := strings.SplitN(s[:len(s)-1], ".", 3)
	if len(ss) > 2 {
		return fmt.Errorf("malformed duration %q: too many decimals", s)
	}
	// hasDigits is set if either the whole or fractional part of the number is
	// present, since both are optional but one is required.
	hasDigits := false
	var sec, ns int64
	if len(ss[0]) > 0 {
		var err error
		if sec, err = strconv.ParseInt(ss[0], 10, 64); err != nil {
			return fmt.Errorf("malformed duration %q: %v", s, err)
		}
		// Maximum seconds value per the durationpb spec.
		const maxProtoSeconds = 315_576_000_000
		if sec > maxProtoSeconds {
			return fmt.Errorf("out of range: %q", s)
		}
		hasDigits = true
	}
	if len(ss) == 2 && len(ss[1]) > 0 {
		if len(ss[1]) > 9 {
			return fmt.Errorf("malformed duration %q: too many digits after decimal", s)
		}
		var err error
		if ns, err = strconv.ParseInt(ss[1], 10, 64); err != nil {
			return fmt.Errorf("malformed duration %q: %v", s, err)
		}
		for i := 9; i > len(ss[1]); i-- {
			ns *= 10
		}
		hasDigits = true
	}
	if !hasDigits {
		return fmt.Errorf("malformed duration %q: contains no numbers", s)

View on GitHub (pinned to 0c51461d27)