JuliusBrussee/caveman · error
JSON number exponent exceeds supported range
Error message
JSON number exponent exceeds supported range
What it means
The exponent overflow guard in proposalrun's JSON number canonicalizer. maxJSONBNumberExponent is 131072+16383 = 147455, mirroring PostgreSQL's jsonb numeric limits (131072 integer digits, 16383 fraction digits). While accumulating the exponent digits left-to-right, the parser checks value > (max-digit)/10 before each multiply-by-10 step; if the running value would exceed the cap it refuses rather than silently wrapping or letting a hostile exponent drive unbounded arithmetic later.
Source
Thrown at shared/platform/proposalrun/proposalrun.go:289
if len(raw) == 0 {
return 0, fmt.Errorf("invalid JSON number exponent")
}
negative := false
if raw[0] == '+' || raw[0] == '-' {
negative = raw[0] == '-'
raw = raw[1:]
}
if len(raw) == 0 {
return 0, fmt.Errorf("invalid JSON number exponent")
}
value := 0
for i := 0; i < len(raw); i++ {
if raw[i] < '0' || raw[i] > '9' {
return 0, fmt.Errorf("invalid JSON number exponent")
}
digit := int(raw[i] - '0')
if value > (maxJSONBNumberExponent-digit)/10 {
return 0, fmt.Errorf("JSON number exponent exceeds supported range")
}
value = value*10 + digit
}
if negative {
return -value, nil
}
return value, nil
}
// formatTime pins the created_at representation: UTC, microsecond resolution
// (Postgres TIMESTAMPTZ stores microseconds, so truncating here makes the value
// hashed at write time equal the value read back at verify time), RFC3339Nano.
func formatTime(t time.Time) string {
return t.UTC().Truncate(time.Microsecond).Format(time.RFC3339Nano)
}
// RowHash computes the pinned canonical row hash:
//View on GitHub (pinned to 27d5a3981a)
Solutions
- Clamp or reject numbers with |exponent| > 147455 before they enter Detail — this ceiling is a hard Postgres jsonb limit, not a tunable.
- If huge magnitudes are legitimate, store the value as a string in Detail rather than a JSON number.
- Validate with a decimal library (e.g. shopspring/decimal or math/big) and quantize/serialize before marshaling the run row.
Example fix
// before
detail := []byte(`{"prob": 1e-1000000}`)
_, err := proposalrun.RowHash(prev, seq, action, detail, cost, createdAt)
// after
// serialize out-of-range magnitudes as strings
detail := []byte(`{"prob": "1e-1000000"}`) Defensive patterns
Strategy: validation
Validate before calling
const maxExp = 131072 + 16383 // mirror of maxJSONBNumberExponent
func exponentInRange(f float64) bool {
if f == 0 { return true }
_, exp := strconv.FormatFloat(f, 'e', -1, 64) // parse exponent via formatting
_ = exp
s := strconv.FormatFloat(f, 'e', -1, 64)
i := strings.IndexAny(s, "eE")
n, _ := strconv.Atoi(s[i+1:])
return n >= -maxExp && n <= maxExp
} Try / catch
if err := proposalrun.VerifyChain(runs); err != nil {
if strings.Contains(err.Error(), "exponent exceeds supported range") {
// re-serialize the offending Detail with out-of-range numbers as strings
}
} Prevention
- Validate numeric ranges before putting values into Detail; serialize out-of-range magnitudes as strings.
- Treat 147455 as a hard Postgres jsonb ceiling when designing the Detail schema.
- If big-precision data is expected, use math/big or shopspring/decimal and quantize before marshal.
When it happens
Trigger: Any canonicalization of a JSON number whose exponent magnitude exceeds 147455, e.g. 1e999999 or 1e-1000000, while computing RowHash or verifying the chain.
Common situations: Round-tripping values produced by arbitrary-precision libraries (big.Float/Decimal with huge exponents) into a proposal Detail; adversarial input crafted to blow up jsonb storage in Postgres; porting data from systems that allow larger exponents than Postgres jsonb.
Related errors
- proposalrun: seq %d recompute: %w
- option not found
- cave_harness_adapter_version_invalid
- cave_harness_upstream_version_invalid
- cave_harness_artifact_digest_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/601376bf42dd20af.
Report an issue: GitHub.