grpc/grpc-go · error
malformed duration %q: contains no numbers
Error message
malformed duration %q: contains no numbers
What it means
This error occurs in Duration.UnmarshalJSON when neither the whole-seconds part nor the fractional part of the duration string contains any digits. The parser requires at least one numeric component. For example, a bare ".s" or just "s" triggers this because hasDigits stays false through both parsing branches.
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 03255a9237)
Solutions
- Provide a complete duration string with at least one numeric digit, e.g. "0s", "1s", or "0.5s".
- Audit the source generating the JSON (control plane, config template) to ensure duration fields are never empty.
- Add pre-validation on the JSON payload before unmarshaling into serviceconfig types.
Example fix
// before (broken): no digits at all "timeout": "s" // after (valid): at least the seconds field "timeout": "0s"
Defensive patterns
Strategy: validation
Validate before calling
// Ensure a duration string has at least one digit before unmarshaling
func hasDurationDigit(s string) bool {
if !strings.HasSuffix(s, "s") { return false }
body := strings.TrimSuffix(s, "s")
if len(body) > 0 && body[0] == '-' { body = body[1:] }
parts := strings.SplitN(body, ".", 2)
return parts[0] != "" || (len(parts) == 2 && parts[1] != "")
} Try / catch
var d serviceconfig.Duration
if err := json.Unmarshal(raw, &d); err != nil {
if strings.Contains(err.Error(), "contains no numbers") {
d = serviceconfig.Duration(0) // default
} else { return err }
} Prevention
- Never use empty strings for duration fields in config.
- Use "0s" as the minimal valid representation of zero duration.
- Validate config payloads from external sources (xDS, files) before applying.
When it happens
Trigger: A JSON field unmarshaled into serviceconfig.Duration contains a value like "s", ".s", "-.s", or an empty-ish string that ends with 's' but has no digits before or after the decimal point.
Common situations: Missing/blank duration values in xDS service config, template substitution errors producing empty duration strings, or deserialization of a struct with zero-value or uninitialized duration fields serialized as bare units.
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 %q: missing seconds unit
- malformed duration %q: too many decimals
- malformed duration %q: %v
- malformed duration %q: too many digits after decimal
- out of range: %q
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/ef2f5ce4f44fdb95.
Report an issue: GitHub.