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

  1. Measure the offending literal's length and trim it to within PostgreSQL numeric bounds (<=131072 integer digits, <=16383 fraction digits).
  2. 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.
  3. Validate detail payloads at ingestion (reject numbers over the bound before RowHash) so hostile inputs fail fast with your own error.
  4. 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

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


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)