JuliusBrussee/caveman · error
proposalrun: seq %d recompute: %w
Error message
proposalrun: seq %d recompute: %w
What it means
A wrapper, not a root cause: VerifyChain calls RowHash(PrevHash, Seq, Action, Detail, CostUSD, CreatedAt) for every row, and RowHash failed for this row before any comparison happened. RowHash canonicalizes the Detail JSON (number re-encoding, key sorting) and hashes; its failures are the JSON canonicalization errors — malformed number exponents, exponent/coefficient overflow, or an unmarshalable Detail value. The %w preserves the underlying error for errors.Is/As inspection.
Source
Thrown at shared/platform/proposalrun/proposalrun.go:361
if len(runs) == 0 {
return nil
}
sorted := make([]Run, len(runs))
copy(sorted, runs)
sort.Slice(sorted, func(i, j int) bool { return sorted[i].Seq < sorted[j].Seq })
prevHash := ""
for i, r := range sorted {
wantSeq := int64(i + 1)
if r.Seq != wantSeq {
return fmt.Errorf("proposalrun: seq %d out of order (expected %d)", r.Seq, wantSeq)
}
if r.PrevHash != prevHash {
return fmt.Errorf("proposalrun: seq %d prev_hash does not link to the prior row", r.Seq)
}
got, err := RowHash(r.PrevHash, r.Seq, r.Action, r.Detail, r.CostUSD, r.CreatedAt)
if err != nil {
return fmt.Errorf("proposalrun: seq %d recompute: %w", r.Seq, err)
}
if got != r.RowHash {
return fmt.Errorf("proposalrun: seq %d row_hash mismatch (tampered)", r.Seq)
}
prevHash = r.RowHash
}
return nil
}
View on GitHub (pinned to 27d5a3981a)
Solutions
- Unwrap the error (errors.Unwrap / inspect the message after 'recompute:') to see which canonicalization rule failed, then fix that field's value in the offending row.
- After changing canonicalization rules, run a migration that re-writes Detail through the new canonicalizer and re-chains rows — old and new hash outputs are not interchangeable.
- Ensure all writes go through the package's own append/insert API so Detail is validated at write time.
Example fix
// before
err := proposalrun.VerifyChain(runs)
fmt.Println(err) // "proposalrun: seq 7 recompute: invalid JSON number exponent"
// after
err := proposalrun.VerifyChain(runs)
var inner error
for e := err; e != nil; e = errors.Unwrap(e) { inner = e }
log.Printf("seq 7 root cause: %v", inner) // then repair the Detail blob at that row Defensive patterns
Strategy: try-catch
Try / catch
if err := proposalrun.VerifyChain(runs); err != nil {
if strings.Contains(err.Error(), "recompute:") {
root := err
for errors.Unwrap(root) != nil { root = errors.Unwrap(root) }
// root is the canonicalization failure; repair the Detail at the reported seq
log.Printf("chain verify: root cause at seq: %v", root)
}
} Prevention
- Route all Detail writes through the package API so canonicalization is validated at write time.
- After upgrading canonicalization rules, migrate and re-hash existing rows in the same release.
- Keep unwrap-friendly wrapping (%w) in your own wrappers so the root cause stays reachable.
When it happens
Trigger: A stored Detail blob contains a JSON number this package cannot canonicalize (e.g. exponent beyond 147455 or a corrupt lexeme), or Detail was written by a different/older canonicalizer version whose output the current one rejects.
Common situations: Upgrading the proposalrun canonicalization rules after rows were already persisted; DB dump/load that mangled JSONB numeric spellings; a writer that bypassed the package's validation when inserting.
Related errors
- JSON number exponent exceeds supported range
- cave_eve_terminal_${result.status}
- cave_harness_upstream_version_mismatch
- cave_mastra_max_steps_invalid
- caveman agent: invalid .caveman/provider.json
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/bb782f2b337b3330.
Report an issue: GitHub.