JuliusBrussee/caveman · error

recordOutcome: evidence must be a plain object

Error message

recordOutcome: evidence must be a plain object

What it means

The evidence field of recordOutcome must be a plain object whose entries become 'key:value' reference strings attached to the outcome. This specific error fires when evidence is not a plain object at all — arrays, null, undefined, strings, or class instances all fail isPlainObject. It is the shape check that runs before per-key validation.

Source

Thrown at packages/mastra/src/index.ts:653

 * confidence outside [0,1], or values that are not plain JSON. Exactly one
 * request is made — no retries — and a non-2xx response throws a
 * {@link CavemanOutcomeError} carrying the status and body verbatim.
 */
export async function recordOutcome(
  client: CavemanOutcomeClient,
  input: RecordOutcomeInput,
): Promise<RecordOutcomeResult> {
  const controlApiUrl = requireBaseUrl(client.controlApiUrl, "recordOutcome: controlApiUrl");
  const token = requireNonEmpty(client.token, "recordOutcome: token");
  const projectId = requireNonEmpty(client.projectId, "recordOutcome: projectId");
  const taskId = requireNonEmpty(input.taskId, "recordOutcome: taskId");
  const contract = requireNonEmpty(input.contract, "recordOutcome: contract");

  if (!isPlainObject(input.values) || Object.keys(input.values).length === 0) {
    throw new Error("recordOutcome: values must be a non-empty plain object");
  }
  if (!isPlainObject(input.evidence)) {
    throw new Error("recordOutcome: evidence must be a plain object");
  }
  const evidenceRefs = Object.entries(input.evidence).map(([key, value]) => {
    if (!key.trim()) throw new Error("recordOutcome: evidence keys must be non-empty");
    if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
      throw new Error(`recordOutcome: evidence.${key} must be a string, number, or boolean`);
    }
    if (typeof value === "number" && !Number.isFinite(value)) {
      throw new Error(`recordOutcome: evidence.${key} must be a finite number`);
    }
    const ref = `${key}:${String(value)}`;
    if (!ref.slice(key.length + 1)) throw new Error(`recordOutcome: evidence.${key} must be non-empty`);
    return ref;
  });
  if (evidenceRefs.length === 0) {
    throw new Error("recordOutcome: evidence must contain at least one reference");
  }
  if (evidenceRefs.length > MAX_EVIDENCE_REFS) {
    throw new Error(`recordOutcome: evidence must contain at most ${MAX_EVIDENCE_REFS} references`);

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Pass a plain object with string/number/boolean values: evidence: { commit: sha }.
  2. If evidence is genuinely absent, the API still requires the object shape — check whether your contract permits a placeholder reference before sending.
  3. Replace class instances with plain object literals (or convert via structured cloning) before calling.

Example fix

// before
await recordOutcome(client, { taskId, contract, values, evidence: null });
// after
await recordOutcome(client, { taskId, contract, values, evidence: { commit: sha } });
Defensive patterns

Strategy: validation

Validate before calling

function isPlainObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v)
    && Object.getPrototypeOf(v) === Object.prototype;
}
// require isPlainObject(input.evidence) before calling recordOutcome

Type guard

function isPlainObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v)
    && Object.getPrototypeOf(v) === Object.prototype;
}

Try / catch

try {
  await recordOutcome(client, input);
} catch (e) {
  if (e instanceof Error && e.message === "recordOutcome: evidence must be a plain object") {
    input.evidence = { note: "none" }; // repair shape, then retry once
    return recordOutcome(client, input);
  }
  throw e;
}

Prevention

When it happens

Trigger: recordOutcome(client, { ..., evidence: null }), evidence: ['commit:abc'], evidence: 'commit:abc', or evidence: new EvidenceMap() (class instance).

Common situations: Optional evidence left null when a run has no evidence, reusing an array 'pairs' structure from elsewhere in the codebase, or TS types bypassed with `as any` letting a non-object through.

Related errors


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