JuliusBrussee/caveman · error
cave_tool_sandbox_source_escapes_root
Error message
cave_tool_sandbox_source_escapes_root
What it means
Thrown while staging the sandbox source graph when a file produced by expandSourceGraph is neither inside the framework dist root (the fast-path 'continue' branch) nor inside the canonical sandbox root after relative-path computation. This catches imports that resolve outside the staged tree, e.g. via symlinks.
Source
Thrown at packages/agent/src/runtime.ts:2292
canonicalRoot,
[canonicalEntry],
false,
[canonicalFrameworkDist],
)].sort();
const staging = await realpath(await mkdtemp(resolve(tmpdir(), "caveman-agent-source-")));
try {
const stagedFiles: string[] = [];
await copyOptionalSandboxFile(
resolve(canonicalRoot, "package.json"),
resolve(staging, "package.json"),
stagedFiles,
);
for (const source of graph) {
if (!escapesRoot(relative(canonicalFrameworkDist, source))) {
continue;
}
const name = relative(canonicalRoot, source);
if (escapesRoot(name)) throw new Error("cave_tool_sandbox_source_escapes_root");
const destination = resolve(staging, name);
await mkdir(dirname(destination), { recursive: true });
await copyFile(source, destination);
stagedFiles.push(destination);
}
const frameworkName = relative(canonicalRoot, canonicalFrameworkDist);
if (!escapesRoot(frameworkName)) {
const frameworkDestination = resolve(staging, frameworkName);
await mkdir(dirname(frameworkDestination), { recursive: true });
await symlink(
canonicalFrameworkDist,
frameworkDestination,
process.platform === "win32" ? "junction" : "dir",
);
stagedFiles.push(frameworkDestination);
}
const nodeModules = await realpath(resolve(canonicalRoot, "node_modules"));
await symlink(View on GitHub (pinned to 27d5a3981a)
Solutions
- Identify the offending file from the staging error context and either inline/copy it under the sandbox root or remove the dependency on it
- For symlinked workspace packages, hoist or build them so real files exist under the root
- Verify frameworkDistRoot points at the actual framework dist directory so its files take the fast path
Defensive patterns
Strategy: validation
Validate before calling
import { realpath, relative } from "node:fs/promises";
async function graphWithinRoots(root: string, files: string[], distRoot: string): Promise<string[]> {
const r = await realpath(root), d = await realpath(distRoot);
const bad: string[] = [];
for (const f of files) {
const real = await realpath(f);
const inRoot = (b: string) => { const rel = relative(b, real); return rel !== "" && !rel.startsWith(".."); };
if (!inRoot(r) && !inRoot(d)) bad.push(f);
}
return bad;
} Type guard
function isSourceEscapesRoot(e: unknown): e is Error {
return e instanceof Error && e.message === "cave_tool_sandbox_source_escapes_root";
} Try / catch
try {
await stageSandboxSourceGraph(root, entry, dist);
} catch (e) {
if (isSourceEscapesRoot(e)) {
// scan imports/symlinks, copy offending files under root, retry
} else throw e;
} Prevention
- Scan the import graph for symlink-escaped files before staging
- Materialize symlinked workspace packages (build/copy) into the sandbox root
- Point frameworkDistRoot at the real dist directory so framework files take the fast path
When it happens
Trigger: expandSourceGraph follows an import to a real file whose realpath is outside both canonicalRoot and canonicalFrameworkDist — commonly a symlinked dependency or a file linked out of the project.
Common situations: Symlinked packages (pnpm workspaces, npm link) resolving module files outside the project root; a source file symlinking to a shared directory; framework dist configured with a wrong path making everything look external.
Related errors
- cave_tool_sandbox_entry_escapes_root
- caveman-code: path escapes the workspace: ${candidate}
- probe returned false
- cave_sandbox_conformance_failed
- cave_live_eval_sandbox_profile_escapes_root
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/24a403c0a52f9647.
Report an issue: GitHub.