{"record":{"id":"584f0c88a2ba35fe","repo":"paperclipai/paperclip","slug":"acpx-runtime-directory-must-not-be-a-filesystem-ro","errorCode":null,"errorMessage":"ACPX runtime directory must not be a filesystem root","messagePattern":"ACPX runtime directory must not be a filesystem root","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts","lineNumber":309,"sourceCode":"  const metadata = await stat(workspacePath);\n  if (!metadata.isDirectory() || workspacePath === dirname(workspacePath)) {\n    throw new Error(\"ACPX working directory must be a non-root directory\");\n  }\n  return workspacePath;\n}\n\nexport async function resolveAcpxRuntimeRoot(\n  runtimeDirectory: string,\n  sessionId: string,\n): Promise<string> {\n  if (!runtimeDirectory.trim())\n    throw new Error(\"ACPX runtime directory is required\");\n  const root = await realpath(runtimeDirectory);\n  const metadata = await stat(root);\n  if (!metadata.isDirectory())\n    throw new Error(\"ACPX runtime directory must be a directory\");\n  if (root === dirname(root))\n    throw new Error(\"ACPX runtime directory must not be a filesystem root\");\n  return join(\n    resolve(root),\n    \"acpx\",\n    acpxRuntimeSessionDirectoryName(sessionId),\n  );\n}\n\n/**\n * Return the stable, filesystem-safe directory name used for one normalized\n * ACPX session below the runtime's `acpx` namespace.\n */\nexport function acpxRuntimeSessionDirectoryName(sessionId: string): string {\n  const readable = sessionId\n    .replace(/[^a-zA-Z0-9._-]/g, \"_\")\n    .replace(/^\\.+$/, \"session\")\n    .slice(0, 80);\n  const suffix = createHash(\"sha256\")\n    .update(sessionId)","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts#L291-L327","documentation":"resolveAcpxRuntimeRoot additionally refuses to use the filesystem root ('/' on POSIX, drive root on Windows) as the runtime base. Because per-session paths are built by appending 'acpx/<session-dir>' under the root, using the root would scatter runtime state across the top of the filesystem. The error is thrown when realpath(runtimeDirectory) equals dirname(realpath(runtimeDirectory)).","triggerScenarios":"Calling runtimeRoot()/resolveAcpxRuntimeRoot with runtimeDirectory = \"/\" (or \"C:\\\\\", or a symlink resolving to the filesystem root).","commonSituations":"An unset/empty config variable defaulting to \"/\"; a shell variable like RUNTIME_DIR=${BASE:-/} expanding to the root; a container mount where the intended volume failed to mount, leaving only \"/\"; misconfigured Docker volume mapping.","solutions":["Set runtimeDirectory to a dedicated, non-root directory (e.g. <data>/runtime).","Check your env/config source: an empty or missing variable is probably falling back to \"/\" — fix the default.","In containers, verify the data volume mounted correctly; a missing mount leaves the path resolving to root.","Add a config-time check that the chosen directory is non-root before constructing the driver."],"exampleFix":"// before\nconst dir = process.env.ACPX_RUNTIME_DIR ?? \"/\"; // root fallback\n// after\nconst dir = process.env.ACPX_RUNTIME_DIR;\nif (!dir || dir === \"/\") throw new Error(\"ACPX_RUNTIME_DIR must be a non-root directory\");","handlingStrategy":"validation","validationCode":"import { isAbsolute, resolve } from \"node:path\";\nconst root = resolve(runtimeDirectory);\nif (root === \"/\" || /^[A-Z]:\\\\?$/i.test(root)) {\n  throw new Error(\"ACPX runtime directory must not be the filesystem root\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never default runtimeDirectory to \"/\"; require an explicit path.","Verify container volumes actually mounted before starting the driver.","Guard env-driven config with a non-root assertion at startup."],"tags":["filesystem","config","path","safety","acpx"],"backgroundTag":"invalid-argument-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}