JuliusBrussee/caveman · error

cave_host_sandbox_nested_under_required

cave_host_sandbox_nested_under_required

Error message

cave_host_sandbox_nested_under_required

What it means

Thrown by visit in validateAgentGraph when a nested agent definition declares sandbox: "host" while an ancestor in the chain has already required sandboxing (sandbox: "required" propagates to children via childSandboxRequired). Host mode would let the nested closure escape containment that an ancestor mandated, so it is rejected.

Solutions

  1. Change the nested agent's sandbox from "host" to "required" (or another contained mode).
  2. Remove sandbox: "required" from the ancestor if host execution is genuinely intended for the whole tree.
  3. Replace the host-sandboxed subagent with a tool that performs the needed work inside the sandbox.

Example fix

// before
const nested = { kind: "agent", sandbox: "host", tools: [] };
const root = { kind: "agent", sandbox: "required", tools: [subagentTool(nested)] };
// after
const nested = { kind: "agent", sandbox: "required", tools: [] };
const root = { kind: "agent", sandbox: "required", tools: [subagentTool(nested)] };
Defensive patterns

Strategy: validation

Validate before calling

function auditsHostSandbox(def: AgentDefinition, required = false): boolean {
  if (required && def.sandbox === "host") return false;
  const childRequired = required || def.sandbox === "required";
  return (def.tools ?? []).every(t => t.runtime?.kind !== "subagent" || auditsHostSandbox(t.runtime.definition, childRequired));
}
if (!auditsHostSandbox(root)) throw new Error("host-sandboxed agent nested under required sandbox");

Try / catch

try {
  validateAgentGraph(root);
} catch (e) {
  if (e instanceof Error && e.message === "cave_host_sandbox_nested_under_required") {
    console.error("A nested agent uses sandbox: 'host' under a required-sandbox ancestor; set the child to 'required'");
  } else throw e;
}

Prevention

When it happens

Trigger: A root or ancestor agent sets sandbox: "required", and any descendant definition (reached through a subagent tool's runtime.definition) sets sandbox: "host".

Common situations: Composing a sandboxed root agent with third-party subagent definitions that default to host mode, or upgrading a root to sandbox: "required" without auditing existing nested agents.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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

Appendix: source

Thrown at packages/agent/src/definition-graph.ts:27

  // reached under a sandbox-required ancestor must be re-checked, not skipped.
  const visited = [new Set<AgentDefinition>(), new Set<AgentDefinition>()];
  const active = new Set<AgentDefinition>();

  const visit = (
    definition: AgentDefinition,
    depth: number,
    sandboxRequired: boolean,
  ): void => {
    if (!definition || definition.kind !== "agent" ||
        !Array.isArray(definition.tools)) {
      throw new Error("cave_agent_definition_invalid");
    }
    if (depth > 8) throw new Error("cave_subagent_depth_limit");
    if (active.has(definition)) throw new Error("cave_subagent_definition_cycle");
    // Host mode is an opt-in the root makes for itself. A descendant cannot use
    // it to run closures outside an ancestor's required containment.
    if (sandboxRequired && definition.sandbox === "host") {
      throw new Error("cave_host_sandbox_nested_under_required");
    }
    const memo = visited[sandboxRequired ? 1 : 0]!;
    if (memo.has(definition)) return;
    active.add(definition);
    const childSandboxRequired = sandboxRequired ||
      definition.sandbox === "required";
    const names = new Set<string>();
    for (const declared of definition.tools) {
      if (!declared || declared.kind !== "tool" ||
          typeof declared.name !== "string") {
        throw new Error("cave_tool_definition_invalid");
      }
      if (names.has(declared.name)) throw new Error("cave_duplicate_tool_name");
      names.add(declared.name);
      if (declared.name.startsWith("cave_")) {
        throw new Error(`cave_reserved_tool_name:${declared.name}`);
      }
      if (typeof Reflect.get(declared, TOOL_IMPLEMENTATION_SOURCE) !== "string") {

View on GitHub (pinned to 3ee70a1026)