{"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/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/tool-worker.ts#L116-L141","documentation":"The sandbox worker only executes a tool when request.allowSideEffects is exactly true, and only for tools whose effect is not \"read\". This guard prevents a sandboxed/subagent request from mutating state (writes, network mutations, external calls) unless the host explicitly opted in. It throws before selected.execute is ever invoked, so no side effect occurred.","triggerScenarios":"Invoking a tool whose definition has effect !== \"read\" (e.g. write/effectful tools) while request.allowSideEffects is undefined or false — e.g. spawning a subagent/sandbox request without the allowSideEffects flag.","commonSituations":"Running a subagent in read-only mode but its prompt calls a write tool; forgetting to pass allowSideEffects when constructing the sandbox request; a tool recently changed from read to write effect so previously-allowed calls now get denied.","solutions":["Set allowSideEffects: true on the request if the side effect is intended and the caller is trusted","Use a read-only alternative tool when the task should not mutate state","Check the tool's definition.effect to know whether it requires the opt-in","If the denial is unexpected, verify the tool definition wasn't changed from effect \"read\" to a mutating effect"],"exampleFix":"// before\nconst request = { tool: 'write_file', params };\n// after\nconst request = { tool: 'write_file', params, allowSideEffects: true };","handlingStrategy":"validation","validationCode":"const tool = definition.tools.find((t) => t.name === request.tool);\nif (tool && tool.effect !== 'read' && request.allowSideEffects !== true) {\n  throw new Error('this tool has side effects; set allowSideEffects: true or use a read-only tool');\n}","typeGuard":"function canExecute(tool, request) {\n  return tool.effect === 'read' || request.allowSideEffects === true;\n}","tryCatchPattern":"try {\n  const result = await runSandboxTool(request);\n} catch (error) {\n  if (error.message === 'cave_sandbox_side_effect_denied') {\n    // surface to the model/user that a read-only mode blocked a mutating tool\n  }\n}","preventionTips":["Set allowSideEffects: true explicitly when the caller is trusted and mutation is intended","Prefer read-only (effect: 'read') tools for subagent/sandboxed contexts","Check definition.effect before dispatching a tool call in read-only mode","Audit tool definitions after refactors so tools don't silently change from read to write"],"tags":["sandbox","permissions","side-effects","tools"],"backgroundTag":"permission-denied","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}