{"record":{"id":"ceafa27d955d8ede","repo":"JuliusBrussee/caveman","slug":"cave-stale-lock-checked-stale-join-run-np","errorCode":null,"errorMessage":"cave_stale_lock:${checked.stale.join(\",\")}: run npm run build to relock","messagePattern":"cave_stale_lock:(.+?): run npm run build to relock","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/cli.ts","lineNumber":959,"sourceCode":"    throw error;\n  }\n  const transformRegistrySha256 = await transformRegistrySHA256();\n  const checked = checkLock(lock, {\n    sourceSha256: loaded.sourceSha256,\n    agentDefinitionSha256: agentDefinitionSHA256(loaded.agent),\n    contextIRSha256: contextIRSHA256(await lowerBuildContext(\n      root,\n      loaded.agent,\n    ).then((value) => value.ir)),\n    evalSuiteSha256: sha256(stableStringify(loaded.evals.filter((item) => item.approved && item.required))),\n    runtimeVersion: FRAMEWORK_VERSION,\n    adapterVersion: PI_ADAPTER_VERSION,\n    upstreamVersion: PI_UPSTREAM_VERSION,\n    transformRegistrySha256,\n    catalogSha256: CATALOG_SHA256,\n  });\n  if (!checked.valid) {\n    throw new Error(`cave_stale_lock:${checked.stale.join(\",\")}: run npm run build to relock`);\n  }\n  process.stdout.write([\n    `lock valid: ${lock.build_sha256}`,\n    `plan: ${lock.selected_plan_id}`,\n    `catalog cost/task: $${lock.evidence.catalog_cost_usd_per_task.toFixed(6)}`,\n    \"local evidence: passed (estimate only)\",\n    \"provider savings: not claimed\",\n    \"\",\n  ].join(\"\\n\"));\n}\n\ntype LoadedBuildInputs = {\n  config: BuildConfig;\n  agent: AgentDefinition;\n  evals: EvalDefinition[];\n  sourceSha256: string;\n};\n","sourceCodeStart":941,"sourceCodeEnd":977,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/cli.ts#L941-L977","documentation":"Thrown by the `check` CLI command when `checkLock` finds that the immutable build lock (.caveman/agent.lock.json) no longer matches the project. The message lists exactly which pinned identities drifted, e.g. `cave_stale_lock:source,eval_suite`; the possible stale keys (from build.ts checkLock) are source, agent_definition, context_ir, eval_suite, runtime, adapter, upstream, transform_registry, external_provenance, catalog. Caveman refuses to print a valid-lock report because locked execution must be reproducible against the exact digests recorded at build time.","triggerScenarios":"Running `caveman check` / `npm run check` after: editing any file in the hashed source graph; changing the agent definition or its lowered Context IR; approving/editing a required eval (only approved+required evals are hashed); upgrading @caveman-ai/agent (runtime/adapter/upstream versions); an engine registry change (transform_registry); regenerating the provider catalog (catalog); or any lock integrity error.","commonSituations":"Pulling a branch that touched agent source without rebuilding; upgrading the framework patch version; editing evals in caveman.config.ts; `caveman setup` updating the engine so the transform registry sha256 changed.","solutions":["Run `npm run build` to regenerate and re-lock (the message says: run npm run build to relock)","If `eval_suite` is listed, approve the changed required evals, then rebuild","If `runtime`/`adapter`/`upstream` are listed, the framework was upgraded — rebuild against the new version","If `transform_registry` is listed, re-run `caveman setup` so the engine registry matches, then rebuild","Run `caveman doctor` to see lock-drift details before rebuilding"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\n// Before relying on a locked build, run the same gate the CLI runs:\nconst lock = JSON.parse(readFileSync(\".caveman/agent.lock.json\", \"utf8\"));\nconst { valid, stale } = checkLock(lock, {\n  sourceSha256, agentDefinitionSha256, contextIRSha256, evalSuiteSha256,\n  runtimeVersion: FRAMEWORK_VERSION, adapterVersion: PI_ADAPTER_VERSION,\n  upstreamVersion: PI_UPSTREAM_VERSION, transformRegistrySha256, catalogSha256: CATALOG_SHA256,\n});\nif (!valid) await exec(\"npm run build\"); // relock before locked execution","typeGuard":null,"tryCatchPattern":"try { await run(\"npm run check\"); } catch (e) {\n  if (e instanceof Error && /^cave_stale_lock:/.test(e.message)) {\n    const staleKeys = e.message.split(\":\")[1].split(\",\"); // e.g. [\"source\",\"eval_suite\"]\n    // map keys to remedies: source/agent_definition/context_ir → rebuild;\n    // eval_suite → re-approve evals then rebuild; transform_registry → caveman setup\n  } else throw e;\n}","preventionTips":["Make `npm run build` a CI step after any source/eval/framework change and before check","Treat .caveman/agent.lock.json as a generated artifact: regenerate, never hand-edit","Run `caveman doctor` in CI to surface lock drift with details before it becomes a throw"],"tags":["build-lock","drift","cli","reproducibility"],"backgroundTag":"stale-build-artifact","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}