JuliusBrussee/caveman · error

cave_sandbox_definition_mismatch

cave_sandbox_definition_mismatch

Error message

cave_sandbox_definition_mismatch

What it means

The sandbox worker recomputes `agentDefinitionSHA256(definition)` over the imported root agent and compares it to `request.rootDefinitionSha256` sent by the parent. A mismatch means the module the worker loaded is not byte-for-byte the definition the parent inspected when it computed the hash — an integrity fail-closed check against definition drift between parent and sandbox.

Source

Thrown at packages/agent/src/tool-worker.ts:109

      request.agentPath.some((item) => typeof item !== "string" ||
        !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(item)) ||
      typeof request.rootDefinitionSha256 !== "string" ||
      !/^[a-f0-9]{64}$/.test(request.rootDefinitionSha256) ||
      typeof request.toolDefinitionSha256 !== "string" ||
      !/^[a-f0-9]{64}$/.test(request.toolDefinitionSha256) ||
      typeof request.tool !== "string" ||
      !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(request.tool) ||
      typeof request.allowSideEffects !== "boolean" ||
      typeof request.allowNetwork !== "boolean") {
    throw new Error("cave_sandbox_request_invalid");
  }
  if (request.allowNetwork !== true) installNetworkDeny();
  const imported = await import(request.entry) as { default?: AgentDefinition; agent?: AgentDefinition };
  let definition = imported.default ?? imported.agent;
  if (!definition || definition.kind !== "agent") throw new Error("cave_sandbox_agent_export_missing");
  validateAgentGraph(definition);
  if (agentDefinitionSHA256(definition) !== request.rootDefinitionSha256) {
    throw new Error("cave_sandbox_definition_mismatch");
  }
  const visited = new Set<AgentDefinition>([definition]);
  for (const name of request.agentPath) {
    const delegated = definition.tools.filter((item) =>
      item.name === name && item.runtime?.kind === "subagent"
    );
    if (delegated.length !== 1) throw new Error("cave_sandbox_unknown_subagent");
    const child = delegated[0]!.runtime!.definition as AgentDefinition;
    if (!child || child.kind !== "agent") {
      throw new Error("cave_sandbox_subagent_definition_invalid");
    }
    if (visited.has(child)) throw new Error("cave_sandbox_subagent_cycle");
    visited.add(child);
    definition = child;
  }
  const selectedTools = definition.tools.filter((item) => item.name === request.tool);
  if (selectedTools.length !== 1 || selectedTools[0]!.runtime?.kind === "subagent") {
    throw new Error("cave_sandbox_unknown_tool");

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Make the AgentDefinition deterministic: no Date.now()/Math.random()/process.env values inside the definition object.
  2. Re-derive and pass the digest from the exact same staged copy the worker will import (same snapshot for digest and import).
  3. Restart the dev session after editing agent modules so parent and worker agree on one immutable snapshot.
  4. Verify parent and worker run the same package version — print agentDefinitionSHA256(definition) in both processes and diff.

Example fix

// before
const definition = { ...base, createdAt: Date.now() }; // hash differs per process

// after
const definition = { ...base, createdAt: FIXED_BUILD_TIMESTAMP }; // stable across parent and worker
Defensive patterns

Strategy: validation

Validate before calling

import { agentDefinitionSHA256 } from "@caveman-ai/agent/build.js";
const def = (await import(entry)).default;
const digest = agentDefinitionSHA256(def);
if (digest !== request.rootDefinitionSha256) throw new Error("refusing to spawn: digest drift");

Type guard

function isStableDefinition(def: object): boolean {
  return agentDefinitionSHA256(def) === agentDefinitionSHA256(structuredClone(def));
}

Prevention

When it happens

Trigger: Parent computes the digest from one version of the source graph, then the worker imports a different version: hot-reloaded dev server rewrote the entry between digest and spawn; the staged per-run copy of the source graph diverged from the parent snapshot; nondeterministic definition construction (Date.now(), random ids, env-dependent fields) baked into the definition object.

Common situations: Editing agent files while a dev session is mid-run; a build/staging race in programmatic required-sandbox runs that copy the source graph; definitions that embed unstable values (timestamps, uuids) so their hash changes every process; mismatched package versions between parent and worker.

Related errors


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