grpc/grpc-go · error
malformed duration : contains no numbers
Error message
malformed duration %q: contains no numbers
What it means
Fires at duration.go:108 when neither the seconds part nor the fractional part of a protobuf Duration JSON string contains any digits. The parser allows either side to be empty ("5s" or ".5s") but requires at least one numeric component; a value with digits in neither is meaningless.
Solutions
- Check the %q value: it should be just 's' or '.s'.
- Provide an explicit numeric value, e.g. "0s" for zero or "1.5s" for a real duration.
- Ensure config defaults emit a complete numeric string rather than a bare unit suffix.
Example fix
// before // "timeout": "s" // no digits anywhere // after // "timeout": "0s" // or a meaningful value like "5s"
Defensive patterns
Strategy: validation
Validate before calling
func requireDurationValue(s string) error {
body := strings.TrimSuffix(s, "s")
body = strings.TrimPrefix(body, "-")
parts := strings.SplitN(body, ".", 2)
for _, p := range parts {
if p != "" && p != strings.Trim(p, " ") {
if _, err := strconv.Atoi(p); err == nil {
return nil // at least one numeric part
}
}
}
return fmt.Errorf("duration %q has no numeric component", s)
} Try / catch
if err := json.Unmarshal(rawConfig, &cfg); err != nil {
if strings.Contains(err.Error(), "contains no numbers") {
// supply a real default like "0s"
}
} Prevention
- Always emit a full numeric value for Duration fields, never a bare 's'.
- Default empty/missing fields to "0s" explicitly.
When it happens
Trigger: A Duration string that, after stripping the trailing 's' and any leading '-', yields only a '.' with nothing around it. Examples: "s", ".s", "-.s", ".". Note "1.s" and ".5s" are fine; this fires only when hasDigits stays false.
Common situations: A config template placeholder that was never substituted and resolved to empty; a field that defaulted to the literal string "s"; an empty string concatenated with an 's' suffix by buggy formatting code.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- malformed duration
- malformed duration : too many digits after decimal
- out of range
- invalid loadBalancingConfig: entry
- duplicated name
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/ef2f5ce4f44fdb95.
Report an issue: GitHub.
Appendix: source
Thrown at internal/serviceconfig/duration.go:108
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)
}
if neg {
sec *= -1
ns *= -1
}
// Maximum/minimum seconds/nanoseconds representable by Go's time.Duration.
const maxSeconds = math.MaxInt64 / int64(time.Second)
const maxNanosAtMaxSeconds = math.MaxInt64 % int64(time.Second)
const minSeconds = math.MinInt64 / int64(time.Second)
const minNanosAtMinSeconds = math.MinInt64 % int64(time.Second)
if sec > maxSeconds || (sec == maxSeconds && ns >= maxNanosAtMaxSeconds) {
*d = Duration(math.MaxInt64)
} else if sec < minSeconds || (sec == minSeconds && ns <= minNanosAtMinSeconds) {
*d = Duration(math.MinInt64)
} else {View on GitHub (pinned to 0c51461d27)