{"record":{"id":"fd0b0dedf422aa60","repo":"grpc/grpc-go","slug":"out-of-range-q","errorCode":null,"errorMessage":"out of range: %q","messagePattern":"out of range: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/serviceconfig/duration.go","lineNumber":90,"sourceCode":"\t\ts = s[1:]\n\t}\n\tss := strings.SplitN(s[:len(s)-1], \".\", 3)\n\tif len(ss) > 2 {\n\t\treturn fmt.Errorf(\"malformed duration %q: too many decimals\", s)\n\t}\n\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)","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/serviceconfig/duration.go#L72-L108","documentation":"Returned by Duration.UnmarshalJSON when the parsed seconds value exceeds the protobuf maximum of 315,576,000,000 seconds (~10,000 years). This enforces the protobuf Duration spec upper bound separately from Go's time.Duration overflow handling; the value is rejected outright rather than clamped.","triggerScenarios":"A duration value whose seconds field is greater than maxProtoSeconds (defined at duration.go:88). Example: \"400000000000s\". Common when milliseconds are mistakenly written as seconds.","commonSituations":"Unit confusion — writing 86400000 meaning milliseconds but parsed as seconds (≈2750 years over the cap); accidentally sending a nanosecond value as seconds; schema generator emitting epoch-style numbers.","solutions":["Convert the value to seconds using the correct source unit (divide ms by 1000, ns by 1e9).","Cap intentionally-large values at or below 315576000000s.","Validate config durations programmatically before sending to gRPC."],"exampleFix":"// before\n{\"timeout\": \"86400000s\"}  // meant 86400000 ms\n\n// after\n{\"timeout\": \"86400s\"}","handlingStrategy":"validation","validationCode":"const maxProtoSeconds = int64(315_576_000_000)\n\nfunc validateProtoDuration(s string) error {\n    body := strings.TrimSuffix(s, \"s\")\n    secStr := strings.SplitN(body, \".\", 2)[0]\n    if secStr != \"\" {\n        sec, err := strconv.ParseInt(secStr, 10, 64)\n        if err != nil { return err }\n        if sec > maxProtoSeconds { return fmt.Errorf(\"seconds %d exceeds protobuf max %d\", sec, maxProtoSeconds) }\n    }\n    return nil\n}","typeGuard":"func withinProtoSecondsRange(s string) bool {\n    body := strings.TrimSuffix(s, \"s\")\n    secStr := strings.SplitN(body, \".\", 2)[0]\n    if secStr == \"\" { return true }\n    sec, err := strconv.ParseInt(secStr, 10, 64)\n    return err == nil && sec <= 315_576_000_000\n}","tryCatchPattern":null,"preventionTips":["Always convert from the source unit to seconds before writing config.","Document the 315576000000s protobuf ceiling in config schemas.","Reject millisecond-style numbers passed as seconds in CI linting."],"tags":["grpc","service-config","duration","validation","range"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}