{"record":{"id":"04b6f85efee55f31","repo":"JuliusBrussee/caveman","slug":"cave-sandbox-side-effect-denied","errorCode":"cave_sandbox_side_effect_denied","errorMessage":"cave_sandbox_side_effect_denied","messagePattern":"cave_sandbox_side_effect_denied","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/tool-worker.ts","lineNumber":134,"sourceCode":"    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\");\n  }\n  const selected = selectedTools[0]!;\n  if (toolDefinitionSHA256(selected) !== request.toolDefinitionSha256) {\n    throw new Error(\"cave_sandbox_tool_definition_mismatch\");\n  }\n  if (selected.effect !== \"read\" && request.allowSideEffects !== true) {\n    throw new Error(\"cave_sandbox_side_effect_denied\");\n  }\n  const value = await selected.execute(request.params as never, AbortSignal.timeout(selected.timeoutMs));\n  writeResult({ ok: true, value });\n} catch (error) {\n  writeResult({ ok: false, code: failureCode(error) });\n}\n","sourceCodeStart":116,"sourceCodeEnd":141,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/tool-worker.ts#L116-L141","documentation":"The sandbox executes the selected tool only if its declared effect is \"read\", or the request explicitly set allowSideEffects to true. Any tool with effect \"write\" (or another non-read effect) under allowSideEffects !== true is refused before execute — this is the sandbox's side-effect containment guarantee.","triggerScenarios":"Calling a tool whose definition declares effect:\"write\" (or \"side_effect\") while the spawning request was built with allowSideEffects:false or omitted it; a run in read-only/observe mode attempting a mutating tool.","commonSituations":"Default sandbox policy is read-only and the developer forgot to opt into side effects for this run; a tool was misdeclared as write when it is actually read-only; CI runs pinned to allowSideEffects:false hitting a write tool.","solutions":["If the mutation is intended, spawn the worker request with allowSideEffects: true (and understand the sandbox then permits writes).","If the tool is genuinely read-only, correct its declaration to effect:\"read\".","Route unintended write attempts to a read-only equivalent tool in the parent's admission logic."],"exampleFix":"// before\n{ tool: \"write_file\", params: {...}, allowSideEffects: false }\n\n// after\neffect === \"read\"\n  ? { tool: \"write_file\", params: {...} }\n  : { tool: \"write_file\", params: {...}, allowSideEffects: true }","handlingStrategy":"validation","validationCode":"const needsSideEffects = selected.effect !== \"read\";\nif (needsSideEffects && request.allowSideEffects !== true) {\n  throw new Error(`tool ${selected.name} declares effect=${selected.effect}; require explicit operator approval`);\n}","typeGuard":"function isReadOnlyTool(t: { effect?: string }): boolean {\n  return t.effect === \"read\";\n}","tryCatchPattern":null,"preventionTips":["Declare effect accurately on every tool.","Gate allowSideEffects:true behind explicit operator consent in your admission layer.","Default runs to read-only; treat this error as the sandbox working, not a bug."],"tags":["sandbox","side-effects","policy","cave"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}