JuliusBrussee/caveman · error

cave_harness_build_mismatch

cave_harness_build_mismatch

Error message

cave_harness_build_mismatch

What it means

prepareLockedHarnessExecution verifies a CaveBuildLock against the harness identity being executed. It throws when the build lock's harness id, adapter_version, or upstream_version does not match the harness/adapterVersion/upstreamVersion passed in. This ensures a pinned build can only run on the exact harness that produced it.

Solutions

  1. Regenerate the build lock for the current harness/adapter/upstream versions.
  2. Pin or reinstall the adapter so adapter_version matches build.harness.adapter_version.
  3. Use the build lock with the harness id it was created for.
  4. Check out/restore matching versions so harness.id and upstream_version align with the lock.

Example fix

// before
await prepareLockedHarnessExecution({ build: oldLock, harness: "cli", adapterVersion: "0.2.0", upstreamVersion: "1.9.0", ... });
// after
const lock = await buildCaveBuildLock({ harness: "cli", adapterVersion: "0.2.0", upstreamVersion: "1.9.0" });
await prepareLockedHarnessExecution({ build: lock, harness: "cli", adapterVersion: "0.2.0", upstreamVersion: "1.9.0", ... });
Defensive patterns

Strategy: try-catch

Validate before calling

if (lock.harness.id !== harnessId || lock.harness.adapter_version !== adapterVersion || lock.harness.upstreamVersion !== upstreamVersion) {
  lock = await rebuildCaveBuildLock({ harness: harnessId, adapterVersion, upstreamVersion });
}

Type guard

function harnessMatches(lock: { harness: { id: string; adapter_version: string; upstream_version: string } }, h: { id: string; adapterVersion: string; upstreamVersion: string }): boolean {
  return lock.harness.id === h.id && lock.harness.adapter_version === h.adapterVersion && lock.harness.upstream_version === h.upstreamVersion;
}

Try / catch

try {
  const prep = prepared({ build: lock, harness, adapterVersion, upstreamVersion, ... });
} catch (err) {
  if (err instanceof Error && err.message === "cave_harness_build_mismatch") {
    const freshLock = await buildCaveBuildLock({ harness, adapterVersion, upstreamVersion });
    return prepared({ build: freshLock, harness, adapterVersion, upstreamVersion, ... });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling prepared or streamAgentInternal with a build lock generated for a different harness id, or after upgrading the adapter_version or the upstream harness version without regenerating the lock.

Common situations: Upgrading the caveman agent or extension so upstream_version/adapter_version changed while an old build lock was reused; running a build lock against the wrong harness entry point; a stale cached lock file checked into the repo.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/40ab9feae62bb15c. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/execution-kernel.ts:76

      selected.reasoning !== expectedReasoning) {
    throw new Error("cave_execution_plan_selection_mismatch");
  }
}

export function prepareLockedHarnessExecution(input: {
  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");

View on GitHub (pinned to 3ee70a1026)