JuliusBrussee/caveman · error
native runtime: invalid decision record
Error message
native runtime: invalid decision record
What it means
After fetching the TaskDecision object from CCR, ExplainDecision decodes its Data as JSON and requires schema == "caveman.native.decision.v1", decision_id equal to the requested id, and non-empty action and reason. A record failing any of these is corrupt or was written by an incompatible writer, so it refuses to explain it.
Source
Thrown at proxy/internal/nativeruntime/explain.go:58
}
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,
}, nil
}
View on GitHub (pinned to 27d5a3981a)
Solutions
- Re-record the decision with the current runtime so the payload carries schema "caveman.native.decision.v1" and the matching decision_id
- Verify the payload: unescape/pretty-print object.Data and check schema, decision_id, action, reason fields
- If reading legacy records, migrate or skip them instead of explaining
Example fix
// before
exp, err := nativeruntime.ExplainDecision(store, id) // legacy record with schema "caveman.native.decision.v0"
// after
// detect and skip legacy records:
obj, _ := store.FindTaskDecision(id)
if !bytes.Contains(obj.Data, []byte("caveman.native.decision.v1")) {
return ErrLegacyDecision // migrate or skip
}
exp, err := nativeruntime.ExplainDecision(store, id) Defensive patterns
Strategy: try-catch
Validate before calling
obj, err := store.FindTaskDecision(decisionID)
if err == nil && !bytes.Contains(obj.Data, []byte("caveman.native.decision.v1")) {
// legacy record: migrate or skip instead of explaining
} Try / catch
exp, err := nativeruntime.ExplainDecision(store, decisionID)
if err != nil {
if strings.Contains(err.Error(), "invalid decision record") {
// corrupt/legacy record: skip, migrate, or re-record
}
return err
} Prevention
- Migrate stored decisions across runtime schema upgrades
- Write decisions only through the runtime's own recorder so schema fields are always consistent
When it happens
Trigger: A decision record whose payload schema string differs (older/newer writer), whose embedded decision_id doesn't match the object it was found under, or with empty action/reason fields; truncated or hand-edited payload bytes that still decode as JSON.
Common situations: Upgrading the runtime across a schema change while reading old stored decisions; two writers recording decisions with different serializers; storage corruption or partial writes.
Related errors
- ccr: typed object content_hash does not match data
- native runtime: invalid decision id
- native runtime: session id is required
- native runtime: socket already active
- native runtime listen: %w
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/0456fe785f76093d.
Report an issue: GitHub.