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

  1. Set sandboxProfile.childProcess to false (or omit it) if the tool does not strictly need to spawn subprocesses.
  2. Run on Linux with the kernel features (namespaces/seccomp) that provide child-process containment.
  3. Run the agent outside restrictive containers (or with --privileged equivalents) so the OS boundary is available.
  4. 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

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


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)