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

  1. Pass the plan exactly as recorded in the lock (use build.selected_plan) instead of re-deriving it.
  2. If the plan legitimately changed, re-run the build/compile step to produce a new lock containing the new selected_plan.
  3. 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

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


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