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
- Read the offending %q value and compare its seconds part against 315,576,000,000.
- Find the field (retry MaxBackoff, timeout, etc.) that produced the value and cap or correct it.
- If the number was meant to be a different unit, re-express it correctly (e.g. milliseconds -> seconds).
- 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
- Clamp computed backoff/timeout values to <= 315576000000 seconds before serializing.
- Unit-test config generation with extreme inputs to catch overflow.
- Distinguish seconds from milliseconds when authoring config.
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
- malformed duration : contains no numbers
- malformed duration : too many digits after decimal
- malformed duration
- duplicated name
- error parsing custom audit logger config
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)