{"record":{"id":"7976c9697ce0a7fe","repo":"JuliusBrussee/caveman","slug":"invalid-json-number","errorCode":null,"errorMessage":"invalid JSON number","messagePattern":"invalid JSON number","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/proposalrun/proposalrun.go","lineNumber":182,"sourceCode":"\t}\n\treturn canonicalJSONNumber(exact), nil\n}\n\n// normalizeJSONNumber converts one JSON number literal to the shortest plain\n// decimal that has exactly the same base-10 value. It deliberately avoids\n// floating-point conversion; all arithmetic is over digit counts and strings.\nfunc normalizeJSONNumber(literal string) (string, error) {\n\tif len(literal) == 0 || len(literal) > maxJSONBNumberLexeme {\n\t\treturn \"\", fmt.Errorf(\"number exceeds the supported JSONB numeric size\")\n\t}\n\tstart := 0\n\tnegative := false\n\tif literal[0] == '-' {\n\t\tnegative = true\n\t\tstart = 1\n\t}\n\tif start == len(literal) {\n\t\treturn \"\", fmt.Errorf(\"invalid JSON number\")\n\t}\n\n\tmantissaEnd := len(literal)\n\texponent := 0\n\tfor i := start; i < len(literal); i++ {\n\t\tif literal[i] != 'e' && literal[i] != 'E' {\n\t\t\tcontinue\n\t\t}\n\t\tif mantissaEnd != len(literal) {\n\t\t\treturn \"\", fmt.Errorf(\"invalid JSON number\")\n\t\t}\n\t\tmantissaEnd = i\n\t\tvar err error\n\t\texponent, err = parseJSONExponent(literal[i+1:])\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/proposalrun/proposalrun.go#L164-L200","documentation":"normalizeJSONNumber rejects a literal consisting of only a minus sign with no following digits ('-'), which cannot be a valid JSON number. Although encoding/json's decoder normally guarantees valid grammar, this defensive check keeps the normalizer total for any literal path. The error is wrapped as 'proposalrun: canonicalize detail: %w' by CanonicalDetail.","triggerScenarios":"A number token '-' (bare minus, no digits) reaching normalizeJSONNumber — normally only possible via a non-decoder code path or a corrupted json.Number built manually as json.Number(\"-\").","commonSituations":"Code that constructs json.RawMessage/detail blobs by string concatenation and produces \"-\" as a value; manual json.Number(\"-\") passed through a custom path; truncation bugs that cut a number mid-token (e.g. slicing a buffer at a bad offset).","solutions":["Find where the detail blob is built and fix the producer so numbers are always complete digit sequences (never a lone '-').","Check for buffer slicing/truncation bugs that could cut a JSON document mid-number.","Validate the detail JSON with encoding/json before calling RowHash; a bare '-' fails json.Decode earlier with 'invalid character' so you get a clearer message.","If you build json.Number values manually, validate with a regex like ^-?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$ first."],"exampleFix":"// before\ndetail := []byte(`{\"delta\":` + signOnly + `}`) // signOnly == \"-\"\n\n// after\nif signOnly == \"-\" { signOnly = \"0\" }\ndetail := []byte(`{\"delta\":` + signOnly + `}`)","handlingStrategy":"validation","validationCode":"var jsonNumberRe = regexp.MustCompile(`^-?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$`)\nfunc validNumberLiteral(s string) bool { return jsonNumberRe.MatchString(s) }","typeGuard":null,"tryCatchPattern":"canon, err := proposalrun.CanonicalDetail(detail)\nif err != nil && strings.Contains(err.Error(), \"invalid JSON number\") {\n    return fmt.Errorf(\"detail contains a malformed number literal: %w\", err)\n}","preventionTips":["Always build detail blobs with encoding/json (json.Marshal), never string concatenation.","Validate any manually constructed json.Number against the JSON number grammar.","Check for buffer truncation bugs that could cut numbers mid-token.","Route all details through CanonicalDetail in tests with malformed-input cases."],"tags":["go","json","malformed-input","number-parsing"],"backgroundTag":"invalid-argument-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}