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
A sandbox profile requesting childProcess: true is refused. The Node-based tool sandbox has no portable mechanism to contain processes that a sandboxed tool spawns (a child could simply exit the sandbox's restrictions), so the grant fails closed instead of running a tool whose descendants would escape containment. There is no configuration that enables it in this runtime.
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 766dce6b13)
Solutions
- Remove childProcess: true and restructure the tool to do its work in-process (import the library instead of shelling out)
- Run that specific tool under the explicit host sandbox mode where real host access is intended and reviewed (subject to your integration's policy)
- Keep the sandboxed variant of the tool limited to pure computation over staged files
- Track descendant-containment support before reintroducing the flag
Example fix
// before
tool({ sandboxProfile: { childProcess: true }, run: async (p) => execFile('git', ...) });
// after: in-process work, or explicit host mode for this tool
tool({ run: async (p) => computeInProcess(p) });
// or a host-mode tool closure for CLI work, per your integration policy Defensive patterns
Strategy: type-guard
Validate before calling
// Strip unsupported grants from the profile before defining the tool
function sanitizeSandboxProfile(profile) {
const { childProcess, ...rest } = profile ?? {};
if (childProcess) logger.warn('childProcess grant is unsupported and was removed');
return rest;
} Type guard
function isSupportedSandboxProfile(profile: unknown): boolean {
if (profile === undefined || profile === null) return true;
if (typeof profile !== 'object') return false;
const p = profile as Record<string, unknown>;
return p.childProcess !== true; // childProcess:true always throws cave_sandbox_child_process_containment_unavailable
} Try / catch
try {
return await runSandboxedTool(params);
} catch (error) {
if (error instanceof Error && error.message === 'cave_sandbox_child_process_containment_unavailable') {
return runInProcessVariant(params); // restructured tool without spawning
}
throw error;
} Prevention
- Never set childProcess: true - there is no supported descendant containment in this runtime
- Rewrite shelling-out tools as in-process library calls before sandboxing them
- Validate sandbox profiles against supported keys at config load time
- Keep CLI-invoking tools in explicitly reviewed host-mode integrations instead
When it happens
Trigger: sandboxProfile: { childProcess: true } on a tool executed through the sandboxed tool runner; porting a tool that shells out (git, compilers, test runners) into the required sandbox; assuming Docker/seatbelt-style process isolation semantics.
Common situations: Migrating tools from a container-based sandbox where spawning was allowed; build/lint tools that invoke CLIs; version upgrades where previously-tolerated profile fields became fatal fail-closed errors.
Related errors
- cave_host_sandbox_nested_under_required
- cave_sandbox_network_egress_unbounded
- cave_harness_model_identity_missing
- caveman build: config must use strict lock and required sand
- cave_live_eval_sandbox_profile_missing
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/c0184cfe41575098.
Report an issue: GitHub.