JuliusBrussee/caveman · error
native runtime: decode decision: %w
Error message
native runtime: decode decision: %w
What it means
nativeruntime.Explain decodes a stored decision object (object.Data) into a DecisionExplanation via json.Unmarshal. This error means the persisted bytes are not valid JSON — the record was truncated, corrupted, or written by an incompatible older schema. It wraps the encoding/json error so the exact offset/reason is preserved.
Source
Thrown at proxy/internal/nativeruntime/explain.go:55
object, err := store.FindTaskDecision(decisionID)
if err != nil {
return DecisionExplanation{}, err
}
var record struct {
Schema string `json:"schema"`
DecisionID string `json:"decision_id"`
TimestampMS int64 `json:"timestamp_ms"`
Action string `json:"action"`
Reason string `json:"reason"`
InputBasis map[string]any `json:"input_basis"`
AlternativesRejected []string `json:"alternatives_rejected"`
TaskStateBefore string `json:"task_state_before"`
TaskStateAfter string `json:"task_state_after"`
Currentness string `json:"currentness"`
RecoveryRef string `json:"recovery_ref"`
}
if err := json.Unmarshal(object.Data, &record); err != nil {
return DecisionExplanation{}, fmt.Errorf("native runtime: decode decision: %w", err)
}
if record.Schema != "caveman.native.decision.v1" || record.DecisionID != decisionID || record.Action == "" || record.Reason == "" {
return DecisionExplanation{}, errors.New("native runtime: invalid decision record")
}
return DecisionExplanation{
Schema: WhySchema,
DecisionID: record.DecisionID,
SessionID: object.SessionID,
TimestampMS: record.TimestampMS,
Action: record.Action,
Reason: record.Reason,
InputBasis: record.InputBasis,
AlternativesRejected: record.AlternativesRejected,
TaskStateBefore: record.TaskStateBefore,
TaskStateAfter: record.TaskStateAfter,
Currentness: record.Currentness,
RecoveryRef: record.RecoveryRef,
}, nilView on GitHub (pinned to 27d5a3981a)
Solutions
- Retry the explain call for a different, recent decisionID to see if only one record is corrupt
- Inspect the stored object bytes for the decisionID to confirm truncation/corruption
- If the store is disposable, remove or rotate the native runtime store so fresh records are written in the current schema
- Check disk space and file permissions that could have caused the original short write
Defensive patterns
Strategy: try-catch
Validate before calling
func looksLikeJSON(b []byte) bool {
return len(b) > 0 && b[0] == '{' && json.Valid(b)
} Try / catch
exp, err := nativeruntime.Explain(ctx, decisionID)
if err != nil {
if strings.Contains(err.Error(), "decode decision") {
// one corrupt record: log decisionID, surface 'explanation unavailable',
// keep serving other decisions — never retry the same corrupt blob
return explainUnavailable(decisionID)
}
return err
} Prevention
- Write decision records atomically (temp file + rename) to avoid truncation
- Version-stamp records (schema field) and migrate stores on upgrade
- Monitor disk space on the store volume
When it happens
Trigger: Calling the explain/why API for a decisionID whose stored object.Data is truncated (crash mid-write), hand-edited, or produced by a newer/older binary with a different serialization; passing a decision ID whose blob collides with non-JSON data.
Common situations: Upgrading the runtime after a schema change without migrating the store; disk-full during an earlier record append; querying a decision whose row was partially written.
Related errors
- generic decode: expected a JSON array or {"data":[...]} of o
- helicone decode: %w
- langfuse decode: %w
- otlp decode: %w
- kms: decode envelope: %w
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/414efd931e97feba.
Report an issue: GitHub.