{"record":{"id":"c7b0e34bf79394fe","repo":"JuliusBrussee/caveman","slug":"cave-sandbox-definition-mismatch","errorCode":"cave_sandbox_definition_mismatch","errorMessage":"cave_sandbox_definition_mismatch","messagePattern":"cave_sandbox_definition_mismatch","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/tool-worker.ts","lineNumber":109,"sourceCode":"      request.agentPath.some((item) => typeof item !== \"string\" ||\n        !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(item)) ||\n      typeof request.rootDefinitionSha256 !== \"string\" ||\n      !/^[a-f0-9]{64}$/.test(request.rootDefinitionSha256) ||\n      typeof request.toolDefinitionSha256 !== \"string\" ||\n      !/^[a-f0-9]{64}$/.test(request.toolDefinitionSha256) ||\n      typeof request.tool !== \"string\" ||\n      !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(request.tool) ||\n      typeof request.allowSideEffects !== \"boolean\" ||\n      typeof request.allowNetwork !== \"boolean\") {\n    throw new Error(\"cave_sandbox_request_invalid\");\n  }\n  if (request.allowNetwork !== true) installNetworkDeny();\n  const imported = await import(request.entry) as { default?: AgentDefinition; agent?: AgentDefinition };\n  let definition = imported.default ?? imported.agent;\n  if (!definition || definition.kind !== \"agent\") throw new Error(\"cave_sandbox_agent_export_missing\");\n  validateAgentGraph(definition);\n  if (agentDefinitionSHA256(definition) !== request.rootDefinitionSha256) {\n    throw new Error(\"cave_sandbox_definition_mismatch\");\n  }\n  const visited = new Set<AgentDefinition>([definition]);\n  for (const name of request.agentPath) {\n    const delegated = definition.tools.filter((item) =>\n      item.name === name && item.runtime?.kind === \"subagent\"\n    );\n    if (delegated.length !== 1) throw new Error(\"cave_sandbox_unknown_subagent\");\n    const child = delegated[0]!.runtime!.definition as AgentDefinition;\n    if (!child || child.kind !== \"agent\") {\n      throw new Error(\"cave_sandbox_subagent_definition_invalid\");\n    }\n    if (visited.has(child)) throw new Error(\"cave_sandbox_subagent_cycle\");\n    visited.add(child);\n    definition = child;\n  }\n  const selectedTools = definition.tools.filter((item) => item.name === request.tool);\n  if (selectedTools.length !== 1 || selectedTools[0]!.runtime?.kind === \"subagent\") {\n    throw new Error(\"cave_sandbox_unknown_tool\");","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/tool-worker.ts#L91-L127","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Make the AgentDefinition deterministic: no Date.now()/Math.random()/process.env values inside the definition object.","Re-derive and pass the digest from the exact same staged copy the worker will import (same snapshot for digest and import).","Restart the dev session after editing agent modules so parent and worker agree on one immutable snapshot.","Verify parent and worker run the same package version — print agentDefinitionSHA256(definition) in both processes and diff."],"exampleFix":"// before\nconst definition = { ...base, createdAt: Date.now() }; // hash differs per process\n\n// after\nconst definition = { ...base, createdAt: FIXED_BUILD_TIMESTAMP }; // stable across parent and worker","handlingStrategy":"validation","validationCode":"import { agentDefinitionSHA256 } from \"@caveman-ai/agent/build.js\";\nconst def = (await import(entry)).default;\nconst digest = agentDefinitionSHA256(def);\nif (digest !== request.rootDefinitionSha256) throw new Error(\"refusing to spawn: digest drift\");","typeGuard":"function isStableDefinition(def: object): boolean {\n  return agentDefinitionSHA256(def) === agentDefinitionSHA256(structuredClone(def));\n}","tryCatchPattern":null,"preventionTips":["Keep definitions free of Date.now()/random/env values.","Hash and import from the same immutable staged snapshot in required-sandbox runs.","Restart dev sessions after editing agent sources."],"tags":["sandbox","integrity","sha256","hot-reload","cave"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}