{"record":{"id":"af39dacfaa05ad02","repo":"JuliusBrussee/caveman","slug":"cave-harness-plan-digest-mismatch","errorCode":"cave_harness_plan_digest_mismatch","errorMessage":"cave_harness_plan_digest_mismatch","messagePattern":"cave_harness_plan_digest_mismatch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/execution-kernel.ts","lineNumber":86,"sourceCode":"  agentId?: string;\n  contextIR: ContextIR;\n  plan: CavePlan;\n}): LockedHarnessPreparation {\n  const build = parseCaveBuildLock(input.build);\n  if (build.harness.id !== input.harness ||\n      build.harness.adapter_version !== input.adapterVersion ||\n      build.harness.upstream_version !== input.upstreamVersion) {\n    throw new Error(\"cave_harness_build_mismatch\");\n  }\n  if (input.agentId !== undefined && build.agent_id !== input.agentId) {\n    throw new Error(\"cave_harness_agent_mismatch\");\n  }\n  if (stableStringify(input.plan) !== stableStringify(build.selected_plan)) {\n    throw new Error(\"cave_harness_plan_mismatch\");\n  }\n  const planSHA256 = sha256(stableStringify(input.plan));\n  if (planSHA256 !== build.plan_sha256) {\n    throw new Error(\"cave_harness_plan_digest_mismatch\");\n  }\n  const contextIRSHA256 = sha256(stableStringify(contextIRToWire(input.contextIR)));\n  if (contextIRSHA256 !== build.context_ir_sha256) {\n    throw new Error(\"cave_harness_context_ir_mismatch\");\n  }\n  const [provider, ...modelParts] = input.plan.model.split(\"/\");\n  const model = modelParts.join(\"/\");\n  if (!provider || !model) throw new Error(\"cave_harness_model_invalid\");\n  return Object.freeze({\n    build,\n    plan: build.selected_plan,\n    planSHA256,\n    contextIRSHA256,\n    provider,\n    model,\n  });\n}\n","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/execution-kernel.ts#L68-L104","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlock.plan_sha256 = sha256(JSON.stringify(plan)); // ad-hoc digest\n// after\nlock.plan_sha256 = sha256(stableStringify(plan)); // same helper as the kernel","handlingStrategy":"validation","validationCode":"import { createHash } from \"node:crypto\";\nimport { stableStringify } from \"./execution-kernel\";\nconst digest = createHash(\"sha256\").update(stableStringify(plan)).digest(\"hex\");\nif (digest !== lock.plan_sha256) {\n  lock = await buildCaveBuildLock({ plan });\n}","typeGuard":"function planDigestMatches(plan: object, lock: { plan_sha256: string }): boolean {\n  return sha256(stableStringify(plan)) === lock.plan_sha256;\n}","tryCatchPattern":"try {\n  await prepared({ build: lock, plan, ... });\n} catch (err) {\n  if (err instanceof Error && err.message === \"cave_harness_plan_digest_mismatch\") {\n    const fresh = await buildCaveBuildLock({ plan });\n    return prepared({ build: fresh, plan, ... });\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["execution-kernel","checksum","build-lock","plan"],"backgroundTag":"checksum-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}