paperclipai/paperclip · error

additional source projectId is not a simple path segment: ${

Error message

additional source projectId is not a simple path segment: ${projectId}

What it means

Thrown during additional-source processing when projectId is empty, or contains a slash, backslash, or '..'. The projectId is used directly in a label and joined into a remote directory path (runtimeRootDir/project-<id>), so it must be a single safe path segment; otherwise it would enable path injection or directory ambiguity. Caught per-source so one bad project does not abort the run.

Source

Thrown at packages/adapter-utils/src/sandbox-managed-runtime.ts:1019

    // their own roots. No workspace, git-history, or `.paperclip-runtime`
    // semantics apply; those stay anchor-only. Per-project failure isolation: one
    // project's confinement or sync failure logs a warning and is skipped, and
    // the run plus the other projects continue. Only a project that stages
    // successfully appears in `additionalSourceDirs`.
    for (const source of input.additionalSources ?? []) {
      const { localPath, projectId } = source;
      const label = `project-${projectId}`;
      try {
        if (!path.posix.isAbsolute(localPath)) {
          throw new Error(`additional source localPath is not an absolute path: ${localPath}`);
        }
        if (
          projectId.length === 0 ||
          projectId.includes("/") ||
          projectId.includes("\\") ||
          projectId.includes("..")
        ) {
          throw new Error(`additional source projectId is not a simple path segment: ${projectId}`);
        }
        const remoteProjectDir = path.posix.join(runtimeRootDir, label);
        await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing referenced project to sandbox");
        await stageConfinedSyncIn({
          files: [{
            sourcePath: localPath,
            targetPath: remoteProjectDir,
            kind: "directory",
            exclude: additionalSourceExclude,
            access: "ro",
          }],
          sourceRoots: [localPath],
          targetRoots: [remoteProjectDir],
          progressLabel: label,
          statusPhase: "config_sync",
          progressBytes: 0,
        });
        additionalSourceDirs[projectId] = remoteProjectDir;

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Generate projectId from a safe charset (alphanumeric, dash, underscore) — e.g. slugify the repo name.
  2. Validate projectId with /^[A-Za-z0-9_-]+$/ at the collection boundary before passing to prepareSandboxManagedRuntime.
  3. For org/repo inputs, join with a non-separator like '-' (e.g. 'org-repo') rather than '/'.
  4. Ensure projectId is always populated (non-empty) for referenced projects.

Example fix

// before
additionalSources: [{ localPath: abs, projectId: 'acme/widgets' }]
// after
additionalSources: [{ localPath: abs, projectId: 'acme-widgets' }]
Defensive patterns

Strategy: validation

Validate before calling

const PROJECT_ID_RE = /^[A-Za-z0-9_-]+$/;
for (const s of additionalSources ?? []) {
  if (!PROJECT_ID_RE.test(s.projectId)) {
    throw new Error(`additionalSource projectId must be a safe segment: ${s.projectId}`);
  }
}

Type guard

function isSafeProjectId(id: string): boolean {
  return typeof id === 'string' && id.length > 0 && /^[A-Za-z0-9_-]+$/.test(id);
}

Prevention

When it happens

Trigger: An additionalSources entry with projectId = '', 'a/b', 'a\b', or '..'. The check also rejects backslashes to keep POSIX remote paths safe.

Common situations: projectId sourced from an unvalidated user input or repo name with a slash; a UUID-style id that accidentally includes a path separator; a default empty string when the field was optional and not set; a multi-segment org/repo slug used verbatim.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/590610b72c0342cb. Report an issue: GitHub.