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
- Regenerate the build lock so plan_sha256 matches the current plan.
- Pass the original untouched plan used when the lock was created.
- Recompute plan_sha256 with the same stableStringify/sha256 helpers if maintaining the lock manually.
- 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
- Always compute digests with the same stableStringify/sha256 helpers the kernel uses.
- Never hand-edit plan_sha256 in lock files; regenerate the lock instead.
- Keep the lock producer and consumer on the same library version.
- Store the original plan alongside the lock so it can be verified before execution.
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
- cave_harness_context_ir_mismatch
- cave_harness_plan_mismatch
- cave_execution_plan_selection_mismatch
- cave_harness_agent_mismatch
- cave_harness_build_mismatch
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)