{"record":{"id":"d1c86549241711cd","repo":"JuliusBrussee/caveman","slug":"cave-sandbox-source-staging-root-required","errorCode":"cave_sandbox_source_staging_root_required","errorMessage":"cave_sandbox_source_staging_root_required","messagePattern":"cave_sandbox_source_staging_root_required","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":4887,"sourceCode":"    await rm(workspace, { recursive: true, force: true });\n  }\n}\n\n/**\n * Above this many per-file `--allow-fs-read` flags, collapse the staged source\n * files to their common ancestor directory. A large project would\n * otherwise blow the OS argument limit (E2BIG) and the tool could not spawn at\n * all. The collapse is safe here: `sourceFiles` are paths inside the per-run\n * STAGED COPY, which already contains only the reachable source graph — never\n * the real project root with its .env and credentials.\n */\nconst SANDBOX_FS_READ_FLAG_THRESHOLD = 1024;\n\nfunction commonAncestorDir(paths: readonly string[]): string {\n  const dirs = paths.map((path) => resolve(dirname(path)));\n  const first = dirs[0];\n  if (first === undefined) {\n    throw new Error(\"cave_sandbox_source_staging_root_required\");\n  }\n  let ancestor = first;\n  while (dirs.some((path) => escapesRoot(relative(ancestor, path)))) {\n    const parent = dirname(ancestor);\n    if (parent === ancestor) {\n      throw new Error(\"cave_sandbox_source_read_root_refused\");\n    }\n    ancestor = parent;\n  }\n  if (dirname(ancestor) === ancestor) {\n    throw new Error(\"cave_sandbox_source_read_root_refused\");\n  }\n  return ancestor;\n}\n\nexport function sandboxSourceReadFlags(\n  sourceFiles: readonly string[],\n  stagingRoot?: string,","sourceCodeStart":4869,"sourceCodeEnd":4905,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/runtime.ts#L4869-L4905","documentation":"commonAncestorDir throws this when its paths array is empty (dirs[0] is undefined). In the production flow it is only reached from sandboxSourceReadFlags after the >1024-file threshold, which implies a non-empty list, so an empty array here means an internal invariant broke: the flag-collapse path executed with zero staged source files. It is a defensive guard, not a configuration error surface.","triggerScenarios":"Directly calling sandboxSourceReadFlags/commonAncestorDir with an empty sourceFiles array in tests or internal tooling; a tool definition whose staged source graph is empty by the time the collapse runs (internal bug).","commonSituations":"Unit tests exercising the collapse helpers without fixtures; custom integrations importing the internals rather than going through the sandbox executor; effectively unreachable through the public run API.","solutions":["Ensure the tool declares at least one source file before computing read flags","If invoking sandboxSourceReadFlags directly, guard the empty list yourself or stay under SANDBOX_FS_READ_FLAG_THRESHOLD (1024)","If hit through the public API, report it as a bug: the executor should never collapse an empty graph"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Guard the empty list before computing collapsed read flags\nfunction safeSourceReadFlags(sourceFiles: readonly string[], stagingRoot?: string) {\n  if (sourceFiles.length === 0) return []; // nothing to grant; avoids the invariant throw\n  return sandboxSourceReadFlags(sourceFiles, stagingRoot);\n}","typeGuard":"function hasSourceFiles(files: readonly string[]): boolean {\n  return Array.isArray(files) && files.length > 0 && files.every((f) => typeof f === 'string' && f.length > 0);\n}","tryCatchPattern":"try {\n  flags = sandboxSourceReadFlags(sourceFiles, stagingRoot);\n} catch (error) {\n  if (error instanceof Error && error.message === 'cave_sandbox_source_staging_root_required') {\n    // empty list at collapse time: internal invariant - report with the file list\n  }\n  throw error;\n}","preventionTips":["Never invoke the flag builder with an empty sourceFiles array","Ensure tools declare at least one source file when they expect staged sources","Prefer the public sandbox executor over importing the internals directly"],"tags":["sandbox","invariant","filesystem","defensive-guard"],"backgroundTag":null,"analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}