grpc/grpc-go · error
out of range: %q
Error message
out of range: %q
What it means
Returned by Duration.UnmarshalJSON when the parsed seconds value exceeds the protobuf maximum of 315,576,000,000 seconds (~10,000 years). This enforces the protobuf Duration spec upper bound separately from Go's time.Duration overflow handling; the value is rejected outright rather than clamped.
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 03255a9237)
Solutions
- Convert the value to seconds using the correct source unit (divide ms by 1000, ns by 1e9).
- Cap intentionally-large values at or below 315576000000s.
- Validate config durations programmatically before sending to gRPC.
Example fix
// before
{"timeout": "86400000s"} // meant 86400000 ms
// after
{"timeout": "86400s"} Defensive patterns
Strategy: validation
Validate before calling
const maxProtoSeconds = int64(315_576_000_000)
func validateProtoDuration(s string) error {
body := strings.TrimSuffix(s, "s")
secStr := strings.SplitN(body, ".", 2)[0]
if secStr != "" {
sec, err := strconv.ParseInt(secStr, 10, 64)
if err != nil { return err }
if sec > maxProtoSeconds { return fmt.Errorf("seconds %d exceeds protobuf max %d", sec, maxProtoSeconds) }
}
return nil
} Type guard
func withinProtoSecondsRange(s string) bool {
body := strings.TrimSuffix(s, "s")
secStr := strings.SplitN(body, ".", 2)[0]
if secStr == "" { return true }
sec, err := strconv.ParseInt(secStr, 10, 64)
return err == nil && sec <= 315_576_000_000
} Prevention
- Always convert from the source unit to seconds before writing config.
- Document the 315576000000s protobuf ceiling in config schemas.
- Reject millisecond-style numbers passed as seconds in CI linting.
When it happens
Trigger: A duration value whose seconds field is greater than maxProtoSeconds (defined at duration.go:88). Example: "400000000000s". Common when milliseconds are mistakenly written as seconds.
Common situations: Unit confusion — writing 86400000 meaning milliseconds but parsed as seconds (≈2750 years over the cap); accidentally sending a nanosecond value as seconds; schema generator emitting epoch-style numbers.
Related errors
- malformed duration %q: too many decimals
- malformed duration %q: %v
- malformed duration %q: too many digits after decimal
- duplicated name
- malformed duration %q: missing seconds unit
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/fd0b0dedf422aa60.
Report an issue: GitHub.