JuliusBrussee/caveman · error · Error
cave_stale_lock: : run npm run build to relock
Error message
cave_stale_lock:${checked.stale.join(",")}: run npm run build to relock What it means
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.
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
Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
// Before relying on a locked build, run the same gate the CLI runs:
const lock = JSON.parse(readFileSync(".caveman/agent.lock.json", "utf8"));
const { valid, stale } = checkLock(lock, {
sourceSha256, agentDefinitionSha256, contextIRSha256, evalSuiteSha256,
runtimeVersion: FRAMEWORK_VERSION, adapterVersion: PI_ADAPTER_VERSION,
upstreamVersion: PI_UPSTREAM_VERSION, transformRegistrySha256, catalogSha256: CATALOG_SHA256,
});
if (!valid) await exec("npm run build"); // relock before locked execution Try / catch
try { await run("npm run check"); } catch (e) {
if (e instanceof Error && /^cave_stale_lock:/.test(e.message)) {
const staleKeys = e.message.split(":")[1].split(","); // e.g. ["source","eval_suite"]
// map keys to remedies: source/agent_definition/context_ir → rebuild;
// eval_suite → re-approve evals then rebuild; transform_registry → caveman setup
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cave_harness_upstream_version_mismatch
- cave_stale_lock:dev_snapshot
- cave_stale_lock:entry
- caveman-cloud MCP changed after setup; refusing destructive…
- caveman-cloud MCP changed during interrupted setup…
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/ceafa27d955d8ede.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/cli.ts:959
throw error;
}
const transformRegistrySha256 = await transformRegistrySHA256();
const checked = checkLock(lock, {
sourceSha256: loaded.sourceSha256,
agentDefinitionSha256: agentDefinitionSHA256(loaded.agent),
contextIRSha256: contextIRSHA256(await lowerBuildContext(
root,
loaded.agent,
).then((value) => value.ir)),
evalSuiteSha256: sha256(stableStringify(loaded.evals.filter((item) => item.approved && item.required))),
runtimeVersion: FRAMEWORK_VERSION,
adapterVersion: PI_ADAPTER_VERSION,
upstreamVersion: PI_UPSTREAM_VERSION,
transformRegistrySha256,
catalogSha256: CATALOG_SHA256,
});
if (!checked.valid) {
throw new Error(`cave_stale_lock:${checked.stale.join(",")}: run npm run build to relock`);
}
process.stdout.write([
`lock valid: ${lock.build_sha256}`,
`plan: ${lock.selected_plan_id}`,
`catalog cost/task: $${lock.evidence.catalog_cost_usd_per_task.toFixed(6)}`,
"local evidence: passed (estimate only)",
"provider savings: not claimed",
"",
].join("\n"));
}
type LoadedBuildInputs = {
config: BuildConfig;
agent: AgentDefinition;
evals: EvalDefinition[];
sourceSha256: string;
};
View on GitHub (pinned to 766dce6b13)