JuliusBrussee/caveman · error · Error
cave_harness_wire_contract_invalid
cave_harness_wire_contract_invalid
Error message
cave_harness_wire_contract_invalid
What it means
`canonicalRecord` round-trips a wire payload through `stableStringify` to produce a canonical form for hashing and freezing. If `stableStringify` throws — classically because the value contains a circular reference or a value JSON cannot represent (function, symbol, BigInt, undefined inside arrays) — the adapter throws `cave_harness_wire_contract_invalid`: the payload does not satisfy the serializable wire contract the harness evidence chain requires. The payload must be plain JSON data.
Source
Thrown at packages/agent/src/adapters.ts:601
// longer authorize execution.
const installed = installedPackageVersion(pkg);
if (installed === undefined) {
throw new Error(`cave_${harness}_upstream_version_unresolvable`);
}
if (installed !== expected) {
throw new Error(`cave_${harness}_upstream_version_unsupported`);
}
if (identity.upstreamVersion !== installed) {
throw new Error(`cave_${harness}_upstream_version_mismatch`);
}
}
function canonicalRecord(value: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>> {
let encoded: string;
try {
encoded = stableStringify(value);
} catch {
throw new Error("cave_harness_wire_contract_invalid");
}
const decoded = JSON.parse(encoded) as unknown;
if (!isRecord(decoded)) throw new Error("cave_harness_wire_contract_invalid");
return decoded;
}
function deepFreeze<T>(value: T): T {
if (value !== null && typeof value === "object") {
Object.freeze(value);
for (const child of Object.values(value)) deepFreeze(child);
}
return value;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
View on GitHub (pinned to 27d5a3981a)
Solutions
- Strip non-serializable values from the record before passing it: keep only strings, numbers, booleans, null, arrays, and plain objects.
- Break circular references (delete parent/back-pointers) or map entities to plain DTOs.
- If you must carry a live object, keep it outside the record — like the adapter itself does with `AbortSignal` in `snapshotRequest`.
Example fix
// before
const payload = { prompt: "hi", client: someLiveClient }; // not serializable
// after
const { client, ...payload } = config; // client stays outside the wire record
const wirePayload = payload; // plain JSON data only Defensive patterns
Strategy: validation
Validate before calling
function isJsonSerializable(value: unknown, seen = new Set()): boolean {
if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
if (typeof value === "object") {
if (seen.has(value)) return false; // circular
seen.add(value);
return Object.values(value).every((v) => isJsonSerializable(v, seen));
}
return false; // function, symbol, bigint, undefined
}
if (!isJsonSerializable(record)) throw new Error("strip non-serializable values first"); Try / catch
try {
await adapter.run(request);
} catch (err) {
if (err instanceof Error && err.message === "cave_harness_wire_contract_invalid") {
// log which record failed, strip live objects, retry with plain data
}
} Prevention
- Keep wire records to plain JSON data; pass live objects through side channels.
- JSON.round-trip payloads in dev builds to catch circular references early.
- Define DTO types for anything crossing the adapter boundary.
When it happens
Trigger: Passing a harness request/record containing a circular object graph; embedding a function, class instance, symbol, or BigInt in what should be a JSON-serializable record; a computed property getter that throws during stringification.
Common situations: Attaching live objects (loggers, clients, DOM nodes) into request metadata; reusing domain entities with parent pointers as wire records; a refactor that moved a closure into a previously-plain config object.
Related errors
- cave_harness_model_invalid
- cave_harness_model_identity_missing
- cave_harness_signal_invalid
- cave_context_ir_invalid
- caveman agent: value is not canonically serializable
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/b5b55159040d475e.
Report an issue: GitHub.