JuliusBrussee/caveman · error · Error

cave_stale_lock:dev_snapshot

Error message

cave_stale_lock:dev_snapshot

What it means

Inside `validLockIdentity`, when the caller supplies `expectedAgent`, the freshly loaded agent definition must hash identically (`agentDefinitionSHA256(loaded.agent) !== agentDefinitionSHA256(expectedAgent)` → throw). The in-memory dev snapshot of the agent no longer matches the definition that the current config/entry produce — typically the agent was edited after the snapshot was taken.

Solutions

  1. Run `npm run build` to relock, then restart the dev process
  2. Restart dev so the expected snapshot is reloaded from the current entry
  3. Avoid editing the entry module between snapshot capture and the locked run
Defensive patterns

Strategy: validation

Validate before calling

// Before a locked dev run, compare the snapshot against a fresh load:
const fresh = await loadAgent(resolve(root, config.entry));
if (agentDefinitionSHA256(fresh) !== agentDefinitionSHA256(expectedAgent)) {
  await exec("npm run build"); // definition drifted — relock first
}

Try / catch

catch (e) { if (e instanceof Error && e.message === "cave_stale_lock:dev_snapshot") { /* restart dev / rebuild to refresh the snapshot */ } else throw e; }

Prevention

When it happens

Trigger: Starting a dev/locked flow with an `expectedAgent` snapshot captured before the entry module was edited (hot reload changed the definition); the config entry now loading a different definition than the dev snapshot holds.

Common situations: Editing the entry file mid-`caveman dev` session; editing the agent definition between `npm run build` and a locked dev run.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/100f614dc0e714a7. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/cli.ts:1057

  const sourceSha256 = await sourceGraphSHA256(root, sourceFiles);
  return { config, agent, evals, sourceSha256 };
}

async function validLockIdentity(
  root: string,
  entry: string,
  expectedAgent?: AgentDefinition,
  beforeSourceHash?: Parameters<typeof loadBuildInputs>[2],
): Promise<CaveBuildLock | undefined> {
  try {
    const lock = await readLock(root);
    const loaded = await loadBuildInputs(root, "caveman.config.ts", beforeSourceHash);
    if (resolve(root, loaded.config.entry) !== resolve(root, entry)) {
      throw new Error("cave_stale_lock:entry");
    }
    if (expectedAgent !== undefined &&
        agentDefinitionSHA256(loaded.agent) !== agentDefinitionSHA256(expectedAgent)) {
      throw new Error("cave_stale_lock:dev_snapshot");
    }
    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: await transformRegistrySHA256(),
      catalogSha256: CATALOG_SHA256,
    });
    if (!checked.valid) throw new Error(`cave_stale_lock:${checked.stale.join(",")}: run npm run build to relock`);
    return lock;
  } catch (error) {

View on GitHub (pinned to 766dce6b13)