JuliusBrussee/caveman · critical · Error
cave_host_sandbox_nested_under_required
cave_host_sandbox_nested_under_required
Error message
cave_host_sandbox_nested_under_required
What it means
Sandbox policy is monotonic: once any ancestor declares sandbox: 'required', no descendant may declare sandbox: 'host'. Host mode is an opt-in the ROOT makes for itself; a child attempting to escape an ancestor's containment by declaring host mode is a policy violation and is rejected during graph validation, before anything runs.
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 27d5a3981a)
Solutions
- Change the descendant's sandbox from 'host' to 'required' (inherit containment) or remove the explicit override
- If host execution is genuinely needed, the ROOT agent must be the one declaring host mode, with no required ancestor above it
- Audit third-party agent definitions for their sandbox field before nesting them under a required root
Example fix
// before
const root = agent({ kind: "agent", sandbox: "required", tools: [
tool({ name: "delegate", runtime: { kind: "subagent", definition: hostChild } }) // hostChild has sandbox: "host"
]});
// after
const containedChild = agent({ kind: "agent", sandbox: "required", tools: [...] });
const root = agent({ kind: "agent", sandbox: "required", tools: [
tool({ name: "delegate", runtime: { kind: "subagent", definition: containedChild } })
]}); Defensive patterns
Strategy: validation
Validate before calling
function assertSandboxMonotonic(def: any, required = false): void {
const nowRequired = required || def.sandbox === "required";
if (required && def.sandbox === "host") {
throw new Error(`agent ${def.name} declares host mode under a required-sandbox ancestor`);
}
for (const t of def.tools ?? []) {
if (t.runtime?.kind === "subagent") assertSandboxMonotonic(t.runtime.definition, nowRequired);
}
} Prevention
- Apply a single sandbox policy at composition time; do not mix host-mode children under required-mode roots
- Audit third-party agent definitions' sandbox field before nesting them
- Only the root definition should ever declare sandbox: 'host'
When it happens
Trigger: Agent A with sandbox: 'required' has a tool whose runtime.definition (subagent) B declares sandbox: 'host'; or a deeper descendant of a required ancestor does so.
Common situations: Composing agents from libraries where one module assumed host privileges while the composition root mandated containment; copy-pasting a host-mode agent under a hardened orchestrator; attempts to get shell access beneath a sandboxed runner.
Related errors
- caveman-code: path escapes the workspace: ${candidate}
- probe returned false
- cave_sandbox_conformance_failed
- cave_live_eval_sandbox_profile_escapes_root
- caveman agent: file source escapes project root
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/501c0aeb57a9fd01.
Report an issue: GitHub.