JuliusBrussee/caveman · error · Error

cave_stale_lock:dev_snapshot

Error message

cave_stale_lock:dev_snapshot

What it means

Thrown inside `validLockIdentity` when an expected `AgentDefinition` was supplied and its `agentDefinitionSHA256` differs from the definition loaded from the lock's config entry. This is the dev-snapshot guard: the lock was minted against an agent definition that no longer matches the in-memory definition the caller is about to run, so lock identity (models, plan, context graph) cannot be trusted for it.

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 27d5a3981a)

Solutions

  1. Run `npm run build` again after any change to the agent definition so the lock's agent digest matches.
  2. If a watcher keeps tripping it, configure the watcher to rebuild (relock) on agent-file change rather than only restarting.
  3. Verify you are not accidentally passing a different AgentDefinition object than the one your config entry exports.

Example fix

# before
vim src/agent.ts   # edit tools
npm run dev         # Error: cave_stale_lock:dev_snapshot

# after
vim src/agent.ts
npm run build && npm run dev
Defensive patterns

Strategy: validation

Try / catch

try {
  await dev(args);
} catch (error) {
  if (error instanceof Error && error.message === "cave_stale_lock:dev_snapshot") {
    // agent definition changed since the last build: run npm run build, then retry
  } else throw error;
}

Prevention

When it happens

Trigger: The dev path calls `validLockIdentity(root, entry, expectedAgent)` after the agent module was hot-reloaded or re-imported with changes; any edit to the agent definition (prompt text, tools, subagents, context sources) between `npm run build` and the dev run changes the hash and trips this guard.

Common situations: Editing `src/agent.ts` while a dev server/watcher holds a previously built lock; HMR re-importing the entry via `importFresh` (the cache-busting query) so a stale lock is compared against fresh code; teammate commits a rebuilt lock but you still run an older working tree.

Related errors


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