JuliusBrussee/caveman · error
number exceeds the supported JSONB numeric size
Error message
number exceeds the supported JSONB numeric size
What it means
normalizeJSONNumber rejects a JSON number literal whose lexeme is empty or longer than maxJSONBNumberLexeme (147456 characters: 131072 integer digits + 16383 fraction digits + 1 + 16 slack). This is a pre-allocation guard: PostgreSQL jsonb stores numbers as numeric with documented bounds, so a literal that huge either cannot be persisted or would drive unbounded intermediate string arithmetic. The error is wrapped by 'proposalrun: canonicalize detail: %w' from CanonicalDetail.
Solutions
- Measure the offending literal's length and trim it to within PostgreSQL numeric bounds (<=131072 integer digits, <=16383 fraction digits).
- If the value is genuinely a big number, serialize it as a JSON string in the detail blob instead of a number — strings are not subject to numeric bounds.
- Validate detail payloads at ingestion (reject numbers over the bound before RowHash) so hostile inputs fail fast with your own error.
- If a producer library is emitting absurd precision, configure it to round (e.g. cap decimal places) before embedding values into detail.
Example fix
// before
detail := []byte(`{"x":` + hugeBigFloatString + `}`)
canon, err := proposalrun.CanonicalDetail(detail) // exceeds JSONB numeric size
// after
detail := []byte(`{"x":"` + hugeBigFloatString + `"}`) // carry as string
canon, err := proposalrun.CanonicalDetail(detail) Defensive patterns
Strategy: validation
Validate before calling
func numberLexemeWithinBounds(detail json.RawMessage) bool {
var v any
dec := json.NewDecoder(bytes.NewReader(detail))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return false
}
ok := true
jsonWalkNumbers(v, func(n json.Number) error {
if len(n.String()) > 147456 {
ok = false
}
return nil
})
return ok
} Try / catch
canon, err := proposalrun.CanonicalDetail(detail)
if err != nil && strings.Contains(err.Error(), "JSONB numeric size") {
return fmt.Errorf("detail number exceeds storage bounds; encode as string: %w", err)
} Prevention
- Cap numeric precision at the producer (e.g. round floats to a fixed number of digits).
- Represent bignum/decimal values as JSON strings in audit details.
- Reject oversized numeric literals at the API boundary before hashing.
- Fuzz-test CanonicalDetail with long numeric lexemes.
When it happens
Trigger: CanonicalDetail/RowHash on detail JSON containing a number literal longer than 147456 characters (e.g. a 500000-digit integer, or a number padded with hundreds of thousands of fraction digits), or a degenerate empty number token reaching the normalizer.
Common situations: Hostile or fuzzed payloads forwarded into tool-call details; a data pipeline accumulating digits (e.g. concatenated precision from a bignum library); accidental string-to-number conversion of a huge identifier; DOS-style inputs attempting to blow up hash computation.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- proposalrun: canonicalize detail
- ciphertext too short
- envelope: encode scope
- envelope: marshal metadata
- envelope: parse metadata
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/5ae5eada8a41111f.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/proposalrun/proposalrun.go:173
}
if value, err := number.Float64(); err == nil && !math.IsInf(value, 0) && !math.IsNaN(value) {
legacy, err := json.Marshal(value)
if err == nil {
legacyExact, legacyErr := normalizeJSONNumber(string(legacy))
if legacyErr == nil && legacyExact == exact {
return canonicalJSONNumber(string(legacy)), nil
}
}
}
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) {View on GitHub (pinned to 3ee70a1026)