{"record":{"id":"0825f25a82d1d272","repo":"paperclipai/paperclip","slug":"additional-source-localpath-is-not-an-absolute-pat","errorCode":null,"errorMessage":"additional source localPath is not an absolute path: ${localPath}","messagePattern":"additional source localPath is not an absolute path: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/adapter-utils/src/remote-managed-runtime.ts","lineNumber":185,"sourceCode":"        baselineSnapshot,\n        restoreGitHistory: preparedWorkspace.gitBacked,\n        onProgress: input.onProgress,\n      });\n    }\n    throw error;\n  }\n\n  // Stage each referenced (additional) project as a plain, read-only tree in its\n  // OWN isolated remote directory (`project-<projectId>`). Additional sources\n  // never get the anchor's git-history/overlay semantics. Per-project failure\n  // isolation: one project's failure logs a warning and is skipped; the run and\n  // the other projects continue (no workspace restore, unlike an asset failure).\n  const additionalSourceDirs: Record<string, string> = {};\n  for (const source of input.additionalSources ?? []) {\n    const { localPath, projectId } = source;\n    try {\n      if (!path.posix.isAbsolute(localPath)) {\n        throw new Error(`additional source localPath is not an absolute path: ${localPath}`);\n      }\n      if (\n        projectId.length === 0 ||\n        projectId.includes(\"/\") ||\n        projectId.includes(\"\\\\\") ||\n        projectId.includes(\"..\")\n      ) {\n        throw new Error(`additional source projectId is not a simple path segment: ${projectId}`);\n      }\n      const remoteDir = path.posix.join(runtimeRootDir, `project-${projectId}`);\n      await syncDirectoryToSsh({\n        spec: input.spec,\n        localDir: localPath,\n        remoteDir,\n        exclude: REMOTE_ADDITIONAL_SOURCE_HEAVY_DIR_EXCLUDES,\n        onProgress: input.onProgress,\n        progressLabel: `project-${projectId}`,\n      });","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/remote-managed-runtime.ts#L167-L203","documentation":"Thrown inside the additional-sources staging loop in remote-managed-runtime when path.posix.isAbsolute(localPath) is false. Additional sources are synced to the remote runtime via rsync-over-ssh and require a posix-absolute local path. The throw is caught per-source and logged as a warning (the source is skipped); it does not fail the whole run.","triggerScenarios":"additionalSources: [{ localPath: \"./libs/foo\", projectId: \"foo\" }] or [{ localPath: \"libs/foo\", projectId: \"foo\" }] — relative paths hit the check at remote-managed-runtime.ts:184-186. The throw is wrapped in a try/catch at line 183/205 that logs `[paperclip] Failed to stage referenced project ${projectId}; skipping it.` and continues with the next source.","commonSituations":"Config that stores project paths relative to a base directory expecting the runtime to resolve them; user input from a CLI that did not call path.resolve; or symlinked paths whose toString is relative. The result is a missing project on the remote rather than a hard failure, which can mask itself as a downstream import error.","solutions":["Resolve localPath to an absolute path before constructing additionalSources: path.resolve(baseDir, source.localPath).","Watch the warning logs ([paperclip] Failed to stage referenced project ...) — a skipped source is reported there, not as a thrown error to your caller.","Validate at config load: assert(path.isAbsolute(source.localPath)) for every additional source.","If you need to keep relative paths in config, resolve them at the boundary that knows the base directory before handing them to the runtime."],"exampleFix":"// before\nadditionalSources: [{ localPath: \"./libs/foo\", projectId: \"foo\" }]\n\n// after\nadditionalSources: [{ localPath: path.resolve(baseDir, \"libs/foo\"), projectId: \"foo\" }]","handlingStrategy":"validation","validationCode":"function normalizeAdditionalSource(source: { localPath: string; projectId: string }, baseDir: string) {\n  const localPath = path.isAbsolute(source.localPath) ? source.localPath : path.resolve(baseDir, source.localPath);\n  if (!path.isAbsolute(localPath)) {\n    throw new Error(`additional source localPath could not be resolved to absolute: ${source.localPath}`);\n  }\n  return { ...source, localPath };\n}\n\nconst normalized = (input.additionalSources ?? []).map((s) => normalizeAdditionalSource(s, baseDir));","typeGuard":"function isAdditionalSource(value: unknown): value is { localPath: string; projectId: string } {\n  return (\n    typeof value === \"object\" && value !== null &&\n    typeof (value as any).localPath === \"string\" &&\n    typeof (value as any).projectId === \"string\"\n  );\n}","tryCatchPattern":"// Per-source failures are already caught and logged as warnings by remote-managed-runtime.\n// Watch for `[paperclip] Failed to stage referenced project` in logs to catch silent skips.\nfor (const source of input.additionalSources ?? []) {\n  if (!path.isAbsolute(source.localPath)) {\n    console.warn(`Skipping non-absolute additional source: ${source.localPath}`);\n  }\n}","preventionTips":["Resolve every additional source's localPath to absolute at the config boundary.","Watch for the [paperclip] Failed to stage referenced project warning — it is the only signal of a skipped source.","Validate path.isAbsolute at config load and reject early."],"tags":["remote-runtime","paths","config-validation","ssh"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}