JuliusBrussee/caveman · error · Error
cave_harness_plan_mismatch
cave_harness_plan_mismatch
Error message
cave_harness_plan_mismatch
What it means
Thrown while preparing a locked harness (packages/agent/src/execution-kernel.ts:82): the plan object supplied at execution time does not stringify-equal the build.selected_plan stored in the CaveBuildLock. Locked execution must run byte-identical plans; any difference (model, reasoning, tool set, context) between the supplied plan and the locked one fails closed.
Source
Thrown at packages/agent/src/execution-kernel.ts:82
build: CaveBuildLock;
harness: string;
adapterVersion: string;
upstreamVersion: string;
agentId?: string;
contextIR: ContextIR;
plan: CavePlan;
}): LockedHarnessPreparation {
const build = parseCaveBuildLock(input.build);
if (build.harness.id !== input.harness ||
build.harness.adapter_version !== input.adapterVersion ||
build.harness.upstream_version !== input.upstreamVersion) {
throw new Error("cave_harness_build_mismatch");
}
if (input.agentId !== undefined && build.agent_id !== input.agentId) {
throw new Error("cave_harness_agent_mismatch");
}
if (stableStringify(input.plan) !== stableStringify(build.selected_plan)) {
throw new Error("cave_harness_plan_mismatch");
}
const planSHA256 = sha256(stableStringify(input.plan));
if (planSHA256 !== build.plan_sha256) {
throw new Error("cave_harness_plan_digest_mismatch");
}
const contextIRSHA256 = sha256(stableStringify(contextIRToWire(input.contextIR)));
if (contextIRSHA256 !== build.context_ir_sha256) {
throw new Error("cave_harness_context_ir_mismatch");
}
const [provider, ...modelParts] = input.plan.model.split("/");
const model = modelParts.join("/");
if (!provider || !model) throw new Error("cave_harness_model_invalid");
return Object.freeze({
build,
plan: build.selected_plan,
planSHA256,
contextIRSHA256,
provider,View on GitHub (pinned to 27d5a3981a)
Solutions
- Pass the plan exactly as recorded in the lock (use build.selected_plan) instead of re-deriving it.
- If the plan legitimately changed, re-run the build/compile step to produce a new lock containing the new selected_plan.
- Diff the supplied plan against lock.selected_plan (stable stringify both) to find which field drifted.
Example fix
// before
prepareLockedHarness({ build: lock, plan: recomputedPlan, /* ... */ });
// after
prepareLockedHarness({ build: lock, plan: lock.selected_plan, /* ... */ }); Defensive patterns
Strategy: validation
Validate before calling
import { stableStringify } from "@caveman-ai/agent"; // or local stable stringify
if (stableStringify(input.plan) !== stableStringify(lock.selected_plan)) {
// either adopt the locked plan or rebuild the lock
input = { ...input, plan: lock.selected_plan };
} Prevention
- Always execute with lock.selected_plan rather than a re-derived plan object.
- Rebuild the lock after every change to model, reasoning, tools, or contexts.
- Treat plan-vs-lock drift as a build pipeline bug: fix the pipeline, never edit the plan at run time.
When it happens
Trigger: Passing a re-computed or edited CavePlan to prepareLockedHarness while using an older lock; changing model/reasoning settings in code after the lock was generated; plan objects whose JSON key order or content differs from the locked snapshot.
Common situations: Editing agent or plan configuration without rebuilding the lock; CI reusing a committed lock with fresh plan computation; hand-modifying plan fields (model string, reasoning level) to 'tune' a locked run.
Related errors
- cave_harness_context_ir_mismatch
- cave_harness_build_mismatch
- cave_harness_plan_digest_mismatch
- cave_harness_model_invalid
- cave_stale_lock:${checked.stale.join(",")}: run npm run buil
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/84172acef61fb107.
Report an issue: GitHub.