{"record":{"id":"ef2f5ce4f44fdb95","repo":"grpc/grpc-go","slug":"malformed-duration-q-contains-no-numbers","errorCode":null,"errorMessage":"malformed duration %q: contains no numbers","messagePattern":"malformed duration %q: contains no numbers","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/serviceconfig/duration.go","lineNumber":108,"sourceCode":"\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}\n\n\t// Maximum/minimum seconds/nanoseconds representable by Go's time.Duration.\n\tconst maxSeconds = math.MaxInt64 / int64(time.Second)\n\tconst maxNanosAtMaxSeconds = math.MaxInt64 % int64(time.Second)\n\tconst minSeconds = math.MinInt64 / int64(time.Second)\n\tconst minNanosAtMinSeconds = math.MinInt64 % int64(time.Second)\n\n\tif sec > maxSeconds || (sec == maxSeconds && ns >= maxNanosAtMaxSeconds) {\n\t\t*d = Duration(math.MaxInt64)\n\t} else if sec < minSeconds || (sec == minSeconds && ns <= minNanosAtMinSeconds) {\n\t\t*d = Duration(math.MinInt64)\n\t} else {","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/serviceconfig/duration.go#L90-L126","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n// \"timeout\": \"s\"      // no digits anywhere\n\n// after\n// \"timeout\": \"0s\"      // or a meaningful value like \"5s\"","handlingStrategy":"validation","validationCode":"func requireDurationValue(s string) error {\n    body := strings.TrimSuffix(s, \"s\")\n    body = strings.TrimPrefix(body, \"-\")\n    parts := strings.SplitN(body, \".\", 2)\n    for _, p := range parts {\n        if p != \"\" && p != strings.Trim(p, \" \") {\n            if _, err := strconv.Atoi(p); err == nil {\n                return nil // at least one numeric part\n            }\n        }\n    }\n    return fmt.Errorf(\"duration %q has no numeric component\", s)\n}","typeGuard":null,"tryCatchPattern":"if err := json.Unmarshal(rawConfig, &cfg); err != nil {\n    if strings.Contains(err.Error(), \"contains no numbers\") {\n        // supply a real default like \"0s\"\n    }\n}","preventionTips":["Always emit a full numeric value for Duration fields, never a bare 's'.","Default empty/missing fields to \"0s\" explicitly."],"tags":["config","duration","service-config","json","protobuf"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}