{"record":{"id":"21772053f02b6650","repo":"temporalio/temporal","slug":"invalid-subsecond-value-v","errorCode":null,"errorMessage":"invalid subsecond value %v","messagePattern":"invalid subsecond value (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"common/nexus/nexusrpc/timestamp.go","lineNumber":42,"sourceCode":"\n// unmarshalTimestamp unmarshals a string into a Time instance. Uses RFC 3339, with some extra validation to ensure that\n// seconds and subseconds are with an expected range.\n// Copied from https://github.com/protocolbuffers/protobuf-go/blob/0b2c87d84c27802dae7248480444e22421ba577d/encoding/protojson/well_known_types.go#L749C1-L826C2\nfunc unmarshalTimestamp(s string) (time.Time, error) {\n\tt, err := time.Parse(time.RFC3339Nano, s)\n\tif err != nil {\n\t\treturn t, err\n\t}\n\t// Validate seconds.\n\tsecs := t.Unix()\n\tif secs < minTimestampSeconds || secs > maxTimestampSeconds {\n\t\treturn t, fmt.Errorf(\"second value out of range: %v\", secs)\n\t}\n\t// Validate subseconds.\n\ti := strings.LastIndexByte(s, '.')  // start of subsecond field\n\tj := strings.LastIndexAny(s, \"Z-+\") // start of timezone field\n\tif i >= 0 && j >= i && j-i > len(\".999999999\") {\n\t\treturn t, fmt.Errorf(\"invalid subsecond value %v\", s)\n\t}\n\treturn t, nil\n}\n","sourceCodeStart":24,"sourceCodeEnd":46,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/nexus/nexusrpc/timestamp.go#L24-L46","documentation":"After validating whole seconds, unmarshalTimestamp also bounds the subsecond component: it locates the '.' and the timezone marker and rejects timestamps whose fractional-second field is longer than 9 digits ('.999999999'). Longer or malformed fractional parts cannot be represented in nanosecond precision and are rejected with this error.","triggerScenarios":"A timestamp header value like 2026-01-02T10:00:00.1234567890123Z (more than 9 fractional digits) or a malformed string where the timezone/index arithmetic finds an oversized subsecond span, from ServeHTTP.","commonSituations":"Non-Go clients emitting picosecond precision timestamps; hand-built headers in tests or scripts; custom formatting with more than nanosecond digits.","solutions":["Truncate the producer's timestamp to nanosecond precision (9 digits) before sending.","Use standard formatters (time.RFC3339Nano) which emit at most 9 fractional digits.","Validate timestamps client-side before putting them in headers.","If the value is not a real timestamp, fix the sender — this often indicates string concatenation bugs."],"exampleFix":"// before\nts := fmt.Sprintf(\"%s.%sZ\", datePart, nanoPart) // nanoPart has 12 digits\n// after\nts := t.UTC().Format(time.RFC3339Nano) // at most 9 fractional digits","handlingStrategy":"validation","validationCode":"if i := strings.IndexByte(s, '.'); i >= 0 {\n    j := strings.IndexAny(s[i:], \"Z-+\")\n    if j < 0 || (j-1) > 9 {\n        return errors.New(\"subsecond precision exceeds 9 digits\")\n    }\n}","typeGuard":"func isRFC3339Nano(s string) bool {\n    _, err := time.Parse(time.RFC3339Nano, s)\n    return err == nil && !strings.Contains(s, \".\") || len(strings.SplitN(strings.Split(s, \"Z\")[0], \".\", 2)) < 2 || len(strings.SplitN(strings.Split(s, \"Z\")[0], \".\", 2)[1]) <= 9\n}","tryCatchPattern":"t, err := time.Parse(time.RFC3339Nano, raw)\nif err != nil {\n    return fmt.Errorf(\"rejecting timestamp header %q: %w\", raw, err)\n}","preventionTips":["Use time.RFC3339Nano formatting which caps fractional digits at 9","Truncate higher-precision clocks (monotonic/picosecond sources) before formatting","Validate header format client-side before sending","Reject nonconforming requests with a 400 at your gateway"],"tags":["nexus","timestamp","validation","http-header"],"backgroundTag":"invalid-timestamp-format","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}