JuliusBrussee/caveman · error

cave_sandbox_side_effect_denied

cave_sandbox_side_effect_denied

Error message

cave_sandbox_side_effect_denied

What it means

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.

Solutions

  1. Set allowSideEffects: true on the request if the side effect is intended and the caller is trusted
  2. Use a read-only alternative tool when the task should not mutate state
  3. Check the tool's definition.effect to know whether it requires the opt-in
  4. If the denial is unexpected, verify the tool definition wasn't changed from effect "read" to a mutating effect

Example fix

// before
const request = { tool: 'write_file', params };
// after
const request = { tool: 'write_file', params, allowSideEffects: true };
Defensive patterns

Strategy: validation

Validate before calling

const tool = definition.tools.find((t) => t.name === request.tool);
if (tool && tool.effect !== 'read' && request.allowSideEffects !== true) {
  throw new Error('this tool has side effects; set allowSideEffects: true or use a read-only tool');
}

Type guard

function canExecute(tool, request) {
  return tool.effect === 'read' || request.allowSideEffects === true;
}

Try / catch

try {
  const result = await runSandboxTool(request);
} catch (error) {
  if (error.message === 'cave_sandbox_side_effect_denied') {
    // surface to the model/user that a read-only mode blocked a mutating tool
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/04b6f85efee55f31. Report an issue: GitHub.

Appendix: source

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

    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");
  }
  const selected = selectedTools[0]!;
  if (toolDefinitionSHA256(selected) !== request.toolDefinitionSha256) {
    throw new Error("cave_sandbox_tool_definition_mismatch");
  }
  if (selected.effect !== "read" && request.allowSideEffects !== true) {
    throw new Error("cave_sandbox_side_effect_denied");
  }
  const value = await selected.execute(request.params as never, AbortSignal.timeout(selected.timeoutMs));
  writeResult({ ok: true, value });
} catch (error) {
  writeResult({ ok: false, code: failureCode(error) });
}

View on GitHub (pinned to 3ee70a1026)