JuliusBrussee/caveman · error

native runtime: invalid decision id

Error message

native runtime: invalid decision id

What it means

ExplainDecision validates the decision id against the strict pattern ^dec_[0-9a-f]{24}$ before touching the store. Ids not matching (wrong prefix, wrong length, uppercase, or non-hex characters) are rejected as invalid rather than looked up — malformed ids never become store queries.

Source

Thrown at proxy/internal/nativeruntime/explain.go:35

// Ledger record. It contains decision basis, never raw prompt or tool output.
type DecisionExplanation struct {
	Schema               string         `json:"schema"`
	DecisionID           string         `json:"decision_id"`
	SessionID            string         `json:"session_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,omitempty"`
}

func ExplainDecision(store *ccr.Store, decisionID string) (DecisionExplanation, error) {
	if !decisionIDPattern.MatchString(decisionID) {
		return DecisionExplanation{}, errors.New("native runtime: invalid decision id")
	}
	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"`
	}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Use the exact dec_-prefixed 24-lowercase-hex id returned when the decision was recorded
  2. Validate client-side with regexp `^dec_[0-9a-f]{24}$` before calling
  3. If ids come through case-insensitive channels, lowercase them first

Example fix

// before
exp, err := nativeruntime.ExplainDecision(store, decisionID) // e.g. "DEC_AABB..." or ""

// after
var decIDRe = regexp.MustCompile(`^dec_[0-9a-f]{24}$`)
if !decIDRe.MatchString(decisionID) {
    return fmt.Errorf("malformed decision id")
}
exp, err := nativeruntime.ExplainDecision(store, decisionID)
Defensive patterns

Strategy: validation

Validate before calling

var decisionIDRe = regexp.MustCompile(`^dec_[0-9a-f]{24}$`)
if !decisionIDRe.MatchString(decisionID) {
    return errors.New("malformed decision id")
}
exp, err := nativeruntime.ExplainDecision(store, decisionID)

Type guard

func isDecisionID(s string) bool {
    re := regexp.MustCompile(`^dec_[0-9a-f]{24}$`)
    return re.MatchString(s)
}

Prevention

When it happens

Trigger: Calling ExplainDecision with a decision id that is empty, has a typo, uses uppercase hex, is a full 64-hex sha256 instead of the 24-hex dec_ id, or was re-encoded (base64, quoted) in transit.

Common situations: Copying a different object's id (ccr_obj_...) instead of the decision id; case-folding middleware uppercasing ids; truncation of the id in logs or terminal copy-paste.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/53a888deb5e0cc24. Report an issue: GitHub.