JuliusBrussee/caveman · error
cave_output_schema_mismatch
cave_output_schema_mismatch
Error message
cave_output_schema_mismatch
What it means
Thrown when the final message parses as JSON but fails TypeBox Value.Check against definition.output.schema. The runtime enforces the declared contract at the boundary instead of returning unvalidated data cast to the schema type.
Source
Thrown at packages/agent/src/runtime.ts:2122
// the honest outcome rather than missing evidence: the caller gets an empty
// answer, zero usage, and the reason the runtime declined to spend.
if (!finalMessage && stopReason === undefined) {
throw new Error("cave_incomplete_evidence: Pi emitted no final assistant message");
}
if (finalMessage &&
(finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted")) {
throw new Error(`cave_provider_terminal_${finalMessage.stopReason}`);
}
const text = finalMessage === undefined ? "" : assistantText(finalMessage);
if (definition.output?.schema && finalMessage !== undefined) {
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch {
throw new Error("cave_output_schema_invalid_json");
}
if (!Value.Check(definition.output.schema, parsed)) {
throw new Error("cave_output_schema_mismatch");
}
}
if (appliedPlan.appliedTransformIDs.length > 0 && !cacheBoundaryKnown) {
cacheBust = true;
markRequestPassThrough(headers, appliedPlan, "cache_boundary_unobserved");
}
for (const child of nestedReceipts) receipt.recordSubagent(child);
// Built once so the result and its receipt cannot disagree about a breach.
const runReceipt = receipt.build({
runId,
agentId: definition.id,
stopReason: stopReason ?? "complete",
meter: budgetMeter,
...(breakers === undefined ? {} : { breakers: breakers.recorded }),
});
const result: RunResult = {
runId,
agentId: definition.id,View on GitHub (pinned to 27d5a3981a)
Solutions
- Log the parsed JSON on failure and diff it against the schema — the mismatch is usually a renamed or missing field
- Add few-shot or explicit field-by-field instructions in the agent instructions for the output shape
- Loosen the schema where the model legitimately varies (Type.Optional, wider union types) if strictness is not required
- Use a model with stronger structured-output adherence or provider-level JSON/schema enforcement if available
Example fix
// before
const Output = Type.Object({ answer: Type.String() }); // model sends { text: "..." }
// after
const Output = Type.Object({ answer: Type.String() });
// instructions: 'Return {"answer": "<one sentence>"} exactly.' Defensive patterns
Strategy: validation
Validate before calling
// Validate candidate outputs yourself before enforcing the strict schema:
import { Value } from "@sinclair/typebox";
const ok = Value.Check(OutputSchema, JSON.parse(text)); Type guard
function isOutputSchemaMismatch(e: unknown): e is Error {
return e instanceof Error && e.message === "cave_output_schema_mismatch";
} Try / catch
try {
return await agent.run(input, opts);
} catch (e) {
if (isOutputSchemaMismatch(e)) {
// capture raw text, loosen schema or improve instructions, retry
} else throw e;
} Prevention
- Give the model a concrete example of the exact JSON shape in instructions
- Use Type.Optional for genuinely optional fields instead of hoping the model omits them
- Log mismatches with the parsed payload to detect schema drift early
When it happens
Trigger: definition.output.schema is set, JSON.parse succeeds, but the parsed value violates the schema — wrong field names, wrong types, missing required properties, extra enum values.
Common situations: Schema drift between what the prompt asks for and the declared schema; model renaming fields; optional vs required mismatches; model wrapping the object in an extra layer (e.g. {"result": ...}).
Related errors
- option not found
- cave_harness_adapter_version_invalid
- cave_harness_model_invalid
- cave_harness_model_identity_missing
- cave_harness_wire_contract_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/88fcb675a8d49ca5.
Report an issue: GitHub.