{"record":{"id":"e2a33d383c3cd8de","repo":"grpc/grpc-go","slug":"malformed-duration-q-too-many-digits-after-decim","errorCode":null,"errorMessage":"malformed duration %q: too many digits after decimal","messagePattern":"malformed duration %q: too many digits after decimal","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/serviceconfig/duration.go","lineNumber":96,"sourceCode":"\t// hasDigits is set if either the whole or fractional part of the number is\n\t// present, since both are optional but one is required.\n\thasDigits := false\n\tvar sec, ns int64\n\tif len(ss[0]) > 0 {\n\t\tvar err error\n\t\tif sec, err = strconv.ParseInt(ss[0], 10, 64); err != nil {\n\t\t\treturn fmt.Errorf(\"malformed duration %q: %v\", s, err)\n\t\t}\n\t\t// Maximum seconds value per the durationpb spec.\n\t\tconst maxProtoSeconds = 315_576_000_000\n\t\tif sec > maxProtoSeconds {\n\t\t\treturn fmt.Errorf(\"out of range: %q\", s)\n\t\t}\n\t\thasDigits = true\n\t}\n\tif len(ss) == 2 && len(ss[1]) > 0 {\n\t\tif len(ss[1]) > 9 {\n\t\t\treturn fmt.Errorf(\"malformed duration %q: too many digits after decimal\", s)\n\t\t}\n\t\tvar err error\n\t\tif ns, err = strconv.ParseInt(ss[1], 10, 64); err != nil {\n\t\t\treturn fmt.Errorf(\"malformed duration %q: %v\", s, err)\n\t\t}\n\t\tfor i := 9; i > len(ss[1]); i-- {\n\t\t\tns *= 10\n\t\t}\n\t\thasDigits = true\n\t}\n\tif !hasDigits {\n\t\treturn fmt.Errorf(\"malformed duration %q: contains no numbers\", s)\n\t}\n\n\tif neg {\n\t\tsec *= -1\n\t\tns *= -1\n\t}","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/serviceconfig/duration.go#L78-L114","documentation":"Fires at duration.go:96 when the fractional (sub-second) part of a protobuf Duration JSON string has more than 9 digits. The protobuf Duration type only carries nanosecond precision, so more than 9 fractional digits cannot be represented and is rejected rather than silently truncated.","triggerScenarios":"A Duration string whose decimal portion (ss[1] after splitting on '.') has length > 9. Examples: \"1.1234567890s\" (10 digits), \"0.0000000001s\", or a value formatted by code that pads fractions to fixed widths greater than 9.","commonSituations":"A serializer that emits microsecond timestamps as Duration strings with 12-15 fractional digits; config generated from time.Duration.Nanoseconds() formatting with too many digits; values copied from a system that uses picosecond/femtosecond precision.","solutions":["Trim the fractional part of the offending value to at most 9 digits.","If you control the producer, format with %09d on the nanosecond component (the library's own MarshalJSON trims trailing zeros to 0/3/6/9 digits).","Round the source time.Duration to nanosecond precision before serializing."],"exampleFix":"// before\n// \"timeout\": \"1.1234567890s\"   // 10 fractional digits\n\n// after\n// \"timeout\": \"1.123456789s\"      // 9 digits max (nanosecond precision)","handlingStrategy":"validation","validationCode":"func formatDuration(d time.Duration) string {\n    // Trim to <= 9 fractional digits; library MarshalJSON already does this.\n    b, _ := json.Marshal(time.Duration(d)) // use canonical encoding\n    return string(b)\n}","typeGuard":null,"tryCatchPattern":"if err := json.Unmarshal(rawConfig, &cfg); err != nil {\n    if strings.Contains(err.Error(), \"too many digits after decimal\") {\n        // trim the fractional part to 9 digits and retry\n    }\n}","preventionTips":["Never format fractional seconds with more than 9 digits; round to nanoseconds.","Use %09d on the nanosecond component, then trim trailing zeros, matching the library."],"tags":["config","duration","service-config","precision","protobuf"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}