JuliusBrussee/caveman · error

cave_harness_plan_digest_mismatch

cave_harness_plan_digest_mismatch

Error message

cave_harness_plan_digest_mismatch

What it means

Beyond structural equality, the kernel verifies the plan's SHA-256 digest (sha256 of the stable stringify) against build.plan_sha256 recorded in the lock. It throws when the computed digest does not match the recorded one, detecting plan content that differs from what the build lock attests.

Solutions

  1. Regenerate the build lock so plan_sha256 matches the current plan.
  2. Pass the original untouched plan used when the lock was created.
  3. Recompute plan_sha256 with the same stableStringify/sha256 helpers if maintaining the lock manually.
  4. Check for version skew between the code that wrote the lock and the code reading it.

Example fix

// before
lock.plan_sha256 = sha256(JSON.stringify(plan)); // ad-hoc digest
// after
lock.plan_sha256 = sha256(stableStringify(plan)); // same helper as the kernel
Defensive patterns

Strategy: validation

Validate before calling

import { createHash } from "node:crypto";
import { stableStringify } from "./execution-kernel";
const digest = createHash("sha256").update(stableStringify(plan)).digest("hex");
if (digest !== lock.plan_sha256) {
  lock = await buildCaveBuildLock({ plan });
}

Type guard

function planDigestMatches(plan: object, lock: { plan_sha256: string }): boolean {
  return sha256(stableStringify(plan)) === lock.plan_sha256;
}

Try / catch

try {
  await prepared({ build: lock, plan, ... });
} catch (err) {
  if (err instanceof Error && err.message === "cave_harness_plan_digest_mismatch") {
    const fresh = await buildCaveBuildLock({ plan });
    return prepared({ build: fresh, plan, ... });
  }
  throw err;
}

Prevention

When it happens

Trigger: prepareLockedHarnessExecution receiving input.plan such that sha256(stableStringify(plan)) !== build.plan_sha256 — the plan content differs from the digest baked into the lock, even if selected_plan happens to compare equal via a custom path.

Common situations: A hand-edited or migrated lock file where selected_plan was updated but plan_sha256 was not recomputed; non-deterministic serialization in a downstream tool producing different bytes; lock generated by an older version with a different stable-stringify output.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

  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,
    model,
  });
}

View on GitHub (pinned to 3ee70a1026)