JuliusBrussee/caveman · error
cave_sandbox_source_read_root_refused
cave_sandbox_source_read_root_refused
Error message
cave_sandbox_source_read_root_refused
What it means
While walking up from the first staged file's directory toward a common ancestor for the collapsed --allow-fs-read grant, the walk reached the filesystem root (parent === ancestor) while some path was still judged to escape it. Since nothing can escape '/', this branch indicates a path/normalization inconsistency and is refused rather than granting a read rooted at the entire filesystem. Defensive invariant guard.
Source
Thrown at packages/agent/src/runtime.ts:4893
* files to their common ancestor directory. A large project would
* otherwise blow the OS argument limit (E2BIG) and the tool could not spawn at
* all. The collapse is safe here: `sourceFiles` are paths inside the per-run
* STAGED COPY, which already contains only the reachable source graph — never
* the real project root with its .env and credentials.
*/
const SANDBOX_FS_READ_FLAG_THRESHOLD = 1024;
function commonAncestorDir(paths: readonly string[]): string {
const dirs = paths.map((path) => resolve(dirname(path)));
const first = dirs[0];
if (first === undefined) {
throw new Error("cave_sandbox_source_staging_root_required");
}
let ancestor = first;
while (dirs.some((path) => escapesRoot(relative(ancestor, path)))) {
const parent = dirname(ancestor);
if (parent === ancestor) {
throw new Error("cave_sandbox_source_read_root_refused");
}
ancestor = parent;
}
if (dirname(ancestor) === ancestor) {
throw new Error("cave_sandbox_source_read_root_refused");
}
return ancestor;
}
export function sandboxSourceReadFlags(
sourceFiles: readonly string[],
stagingRoot?: string,
): string[] {
if (sourceFiles.length <= SANDBOX_FS_READ_FLAG_THRESHOLD) {
return sourceFiles.map((path) => `--allow-fs-read=${path}`);
}
if (stagingRoot === undefined) {
throw new Error("cave_sandbox_source_staging_root_required");View on GitHub (pinned to 766dce6b13)
Solutions
- Pass consistently resolved absolute paths (resolve() each path first)
- Use the staged copies produced by the executor instead of hand-built path lists
- If hit through the public API, capture the sourceFiles list and report it as a staging bug
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check: consistently resolved absolute paths sharing a non-root ancestor
import { resolve, dirname } from 'node:path';
function precheckSourceFiles(files: readonly string[]): void {
const dirs = files.map((f) => resolve(dirname(f)));
if (new Set(dirs.map((d) => d.split('/')[1])).size > 1) {
throw new Error('source files span multiple top-level trees; re-stage under one root');
}
} Try / catch
try {
flags = sandboxSourceReadFlags(sourceFiles, stagingRoot);
} catch (error) {
if (error instanceof Error && error.message === 'cave_sandbox_source_read_root_refused') {
// ancestor walk hit the filesystem root: capture the path list and report a staging bug
}
throw error;
} Prevention
- Resolve every path to an absolute POSIX form before passing it in
- Avoid mixing hand-built path lists with the executor's staged copies
- If reached via the public API, capture sourceFiles - it indicates a staging/normalization bug
When it happens
Trigger: Path normalization mismatches (mixed absolute/relative or symlink-resolved inputs) passed directly into commonAncestorDir; synthetic or malformed paths in tests; not reachable via the public executor with a correctly staged graph.
Common situations: Direct imports of commonAncestorDir with hand-built path lists; platform-specific path separators (Windows backslash vs POSIX) mixed into one list; internal bug in staging.
Related errors
- cave_sandbox_source_staging_root_required
- caveman agent: dev ${kind} escapes project root
- caveman agent: dev ${kind} symlink escapes project root
- cave_sandbox_source_read_grant_escapes_staging
- caveman build: config must use strict lock and required sand
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/847c94030e377d81.
Report an issue: GitHub.