JuliusBrussee/caveman · error
invalid JSON number
Error message
invalid JSON number
What it means
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.
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.
Example fix
// before
detail := []byte(`{"delta":` + signOnly + `}`) // signOnly == "-"
// after
if signOnly == "-" { signOnly = "0" }
detail := []byte(`{"delta":` + signOnly + `}`) Defensive patterns
Strategy: validation
Validate before calling
var jsonNumberRe = regexp.MustCompile(`^-?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?$`)
func validNumberLiteral(s string) bool { return jsonNumberRe.MatchString(s) } Try / catch
canon, err := proposalrun.CanonicalDetail(detail)
if err != nil && strings.Contains(err.Error(), "invalid JSON number") {
return fmt.Errorf("detail contains a malformed number literal: %w", err)
} Prevention
- 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.
When it happens
Trigger: 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("-").
Common situations: 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).
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- invalid JSON number exponent
- envelope: encode scope
- envelope: marshal metadata
- envelope: parse metadata
- json splice: replacements for candidates
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/7976c9697ce0a7fe.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/proposalrun/proposalrun.go:182
}
return canonicalJSONNumber(exact), nil
}
// normalizeJSONNumber converts one JSON number literal to the shortest plain
// decimal that has exactly the same base-10 value. It deliberately avoids
// floating-point conversion; all arithmetic is over digit counts and strings.
func normalizeJSONNumber(literal string) (string, error) {
if len(literal) == 0 || len(literal) > maxJSONBNumberLexeme {
return "", fmt.Errorf("number exceeds the supported JSONB numeric size")
}
start := 0
negative := false
if literal[0] == '-' {
negative = true
start = 1
}
if start == len(literal) {
return "", fmt.Errorf("invalid JSON number")
}
mantissaEnd := len(literal)
exponent := 0
for i := start; i < len(literal); i++ {
if literal[i] != 'e' && literal[i] != 'E' {
continue
}
if mantissaEnd != len(literal) {
return "", fmt.Errorf("invalid JSON number")
}
mantissaEnd = i
var err error
exponent, err = parseJSONExponent(literal[i+1:])
if err != nil {
return "", err
}
}View on GitHub (pinned to 3ee70a1026)