JuliusBrussee/caveman · error
cave_sandbox_child_process_containment_unavailable
cave_sandbox_child_process_containment_unavailable
Error message
cave_sandbox_child_process_containment_unavailable
What it means
The sandbox runner only supports child-process containment when the underlying OS boundary can provide it; if a tool's sandbox profile requests profile.childProcess === true in an environment where that containment cannot be provided, the runtime fails closed with this error instead of running the tool with processes escaping the sandbox. It is a hard safety refusal, not a transient failure.
Solutions
- Set sandboxProfile.childProcess to false (or omit it) if the tool does not strictly need to spawn subprocesses.
- Run on Linux with the kernel features (namespaces/seccomp) that provide child-process containment.
- Run the agent outside restrictive containers (or with --privileged equivalents) so the OS boundary is available.
- If the tool must spawn processes, restructure it to do its work in-process or via the tool host rather than inside the sandboxed tool.
Example fix
// before
await runTool(parser, { sandboxProfile: { childProcess: true } });
// after: let the tool run without child-process spawns
await runTool(parser, { sandboxProfile: { childProcess: false } }); Defensive patterns
Strategy: validation
Validate before calling
if (profile?.childProcess === true && !platformSupportsChildProcessContainment()) {
throw new Error('childProcess containment unavailable on this platform; disable the flag');
} Type guard
const childProcessContainable = (profile) => !profile?.childProcess || (process.platform === 'linux' && kernelNamespacesAvailable());
Try / catch
try {
await runTool(tool, { sandboxProfile: profile });
} catch (e) {
if (e.message === 'cave_sandbox_child_process_containment_unavailable') {
await runTool(tool, { sandboxProfile: { ...profile, childProcess: false } });
} else throw e;
} Prevention
- Only set childProcess: true on Linux hosts with namespace support.
- Design tools to avoid spawning subprocesses.
- Test sandbox profiles on the target platform before deploying.
When it happens
Trigger: Executing a sandboxed tool whose RunOptions.sandboxProfile sets { childProcess: true } (runtime.ts:4736) on a platform or runtime build where child-process containment is unavailable (e.g. no namespace/seccomp support, unsupported OS).
Common situations: Running on macOS or Windows where the Linux namespace-based child-process containment is absent; running inside containers without the required kernel features; upgrading to a version that removed a weaker childProcess implementation and now fails closed.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
- cave_sandbox_network_egress_unbounded
- cave_host_sandbox_nested_under_required
- cave_live_eval_sandbox_profile_escapes_root
- cave_sandbox_conformance_failed
- cave_sandbox_credential_capability_ambiguous
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/c0184cfe41575098.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/runtime.ts:4736
ledger.reservedUsd = Math.max(0, ledger.reservedUsd - ceilingUsd);
}
}
async function executeSandboxedTool(
entryPath: string,
sourceFiles: readonly string[],
stagingRoot: string,
toolName: string,
params: unknown,
timeoutMs: number,
allowSideEffects: boolean,
profile: RunOptions["sandboxProfile"],
executionContext: InternalExecutionContext,
toolDefinitionSha256: string,
signal?: AbortSignal,
): Promise<unknown> {
if (profile?.childProcess === true) {
throw new Error("cave_sandbox_child_process_containment_unavailable");
}
// `network: true` used to skip the OS network namespace entirely, granting the
// tool UNRESTRICTED egress while credentials sit in its env — an exfiltration
// hole, not a feature. There is no scoped-egress mechanism
// yet (a parent-owned CONNECT proxy bound to an allow-list is the tracked
// follow-up), so unbounded egress fails closed rather than being granted. Every
// sandboxed tool now runs under the OS boundary below.
if (profile?.network === true) {
throw new Error("cave_sandbox_network_egress_unbounded");
}
const requestedCredentialEnv = profile?.credentialEnv ?? [];
const childEnv = buildSandboxToolEnv(requestedCredentialEnv);
// Validate every collapsed grant before allocating per-call state. Refused
// roots must fail without leaving a caveman-agent-tool-* workspace behind.
const sourceReadFlags = sandboxSourceReadFlags(sourceFiles, stagingRoot);
const workspace = await realpath(await mkdtemp(`${tmpdir()}/caveman-agent-tool-`));
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const worker = fileURLToPath(new URL("./tool-worker.js", import.meta.url));View on GitHub (pinned to 3ee70a1026)