grpc/grpc-go · error
malformed duration %q: %v
Error message
malformed duration %q: %v
What it means
Returned by Duration.UnmarshalJSON when strconv.ParseInt fails on the integer-seconds portion of the duration. This happens when the seconds part is non-numeric, empty in an invalid position, or exceeds int64 range. The wrapped error is the strconv error, surfaced as part of the malformed-duration message.
Source
Thrown at internal/serviceconfig/duration.go:85
return fmt.Errorf("malformed duration %q: missing seconds unit", s)
}
neg := false
if s[0] == '-' {
neg = true
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 *= 10View on GitHub (pinned to 03255a9237)
Solutions
- Ensure the seconds component is a base-10 integer within int64 range.
- Strip any non-digit characters (units, placeholders) from the value before writing config.
- Use protojson/serviceconfig.Duration marshaling to produce well-formed values.
Example fix
// before
{"timeout": "5m"}
// after
{"timeout": "300s"} Defensive patterns
Strategy: validation
Validate before calling
func validateProtoDuration(s string) error {
body := strings.TrimSuffix(s, "s")
parts := strings.SplitN(body, ".", 2)
if len(parts[0]) > 0 {
if _, err := strconv.ParseInt(parts[0], 10, 64); err != nil {
return fmt.Errorf("non-integer seconds in %q: %w", s, err)
}
}
var d serviceconfig.Duration
return d.UnmarshalJSON([]byte(`"` + s + `"`))
} Type guard
func secondsPartIsInt64(s string) bool {
body := strings.TrimSuffix(s, "s")
sec := strings.SplitN(body, ".", 2)[0]
if sec == "" { return true }
_, err := strconv.ParseInt(sec, 10, 64)
return err == nil
} Prevention
- Never write Go time.ParseDuration tokens (5m, 1h) into protobuf JSON config.
- Marshal durations programmatically to avoid placeholder leakage.
- Unit-test config files through serviceconfig.Duration before deploy.
When it happens
Trigger: A value like "xs" (non-numeric seconds), "999999999999999999999999s" (overflows int64), or "" patterns that pass the suffix/decimal checks but fail integer parsing at duration.go:84.
Common situations: Config templating substituted a placeholder or unit suffix into the seconds field; copy/paste of a Go duration like "5m" where 'm' lands in the seconds slot; numeric overflow from mis-scaled milliseconds.
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: too many decimals
- malformed duration %q: too many digits after decimal
- malformed duration %q: missing seconds unit
- out of range: %q
- malformed duration %q: contains no numbers
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/5ab2fe5e0a317a4f.
Report an issue: GitHub.