{"record":{"id":"c505457db6544590","repo":"JuliusBrussee/caveman","slug":"caveman-agent-dev-kind-escapes-project-root","errorCode":null,"errorMessage":"caveman agent: dev ${kind} escapes project root","messagePattern":"caveman agent: dev (.+?) escapes project root","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/dev-loader.ts","lineNumber":146,"sourceCode":"async function copyOptional(source: string, destination: string): Promise<void> {\n  try {\n    await readFile(source);\n    await mkdir(dirname(destination), { recursive: true });\n    await copyFile(source, destination);\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n  }\n}\n\nasync function copyProjectFile(\n  root: string,\n  staging: string,\n  source: string,\n  kind: \"source graph\" | \"file source\",\n): Promise<void> {\n  const name = relative(root, source);\n  if (escapesRoot(name)) {\n    throw new Error(`caveman agent: dev ${kind} escapes project root`);\n  }\n  const canonicalSource = await realpath(source);\n  if (escapesRoot(relative(root, canonicalSource))) {\n    throw new Error(`caveman agent: dev ${kind} symlink escapes project root`);\n  }\n  const destination = resolve(staging, name);\n  await mkdir(dirname(destination), { recursive: true });\n  await copyFile(canonicalSource, destination);\n}\n\nfunction remapDevError(error: unknown, staging: string, root: string): unknown {\n  if (!(error instanceof Error)) return error;\n  const mapped = new Error(error.message, { cause: error.cause });\n  mapped.name = error.name;\n  mapped.stack = (error.stack ?? `${error.name}: ${error.message}`).replaceAll(staging, root);\n  return mapped;\n}\n","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/dev-loader.ts#L128-L164","documentation":"Thrown by copyProjectFile in the dev loader (packages/agent/src/dev-loader.ts:146): while staging the immutable dev snapshot, a project-relative source path (a FileSource instruction/context file or a source-graph module) resolves outside the project root. Dev staging only copies files under the project root, so any '..'-escaping or absolute path is rejected.","triggerScenarios":"A FileSource path (agent instructions or context source) pointing outside the root via '../', or an absolute path; or a module pulled into the project source graph that lives outside the root. The message tells you whether it came from the 'source graph' or a 'file source'.","commonSituations":"Instructions loaded from a shared monorepo package or a home-directory prompt file; contexts referencing ../../docs; workspaces where the agent root was configured as a subdirectory while imports reach a sibling package; passing an absolute path like /etc/... or /home/user/prompt.md.","solutions":["Move the referenced file inside the project root and reference it with a root-relative path.","If the file legitimately lives elsewhere in a monorepo, widen the configured project/dev root so the file is inside it.","Inline the content as a string instruction instead of a FileSource if the file cannot be moved."],"exampleFix":"// before\nagent({ instructions: file(\"../shared/prompts/system.md\"), /* ... */ });\n\n// after\nagent({ instructions: file(\"prompts/system.md\"), /* ... */ }); // move the file under the project root","handlingStrategy":"validation","validationCode":"import { relative, isAbsolute } from \"node:path\";\nfunction escapesRoot(root: string, source: string): boolean {\n  const rel = relative(root, source);\n  return rel === \"..\" || rel.startsWith(\"..\") || isAbsolute(rel);\n}\n// check every FileSource path before dev/build:\nfor (const p of agentFileSourcePaths(definition)) {\n  if (escapesRoot(projectRoot, p)) throw new Error(`move ${p} inside ${projectRoot}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep all prompt/context files under the project root; use root-relative paths.","In monorepos, configure the dev root to the workspace root if sources span packages.","Run a preflight check of FileSource paths when they come from user configuration."],"tags":["dev","filesystem","path-traversal","staging"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}