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

  1. Identify the offending file from the staging error context and either inline/copy it under the sandbox root or remove the dependency on it
  2. For symlinked workspace packages, hoist or build them so real files exist under the root
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/24a403c0a52f9647. Report an issue: GitHub.