paperclipai/paperclip · error

Refusing to materialize a skill root that is itself a…

Error message

Refusing to materialize a skill root that is itself a symlink.

What it means

Safety guard in the Paperclip skill materialization copy helper: before copying, the routine resolves source and target roots and computes relative paths between them; when the resolved source root itself is a symlink (or source/target nest inside each other), it refuses to copy. This prevents aliasing hazards where a symlinked skill root would make 'copying' clobber the original location or escape the intended destination tree. It is a fail-fast input validation, not a runtime fault.

Solutions

  1. Use a real directory (not a symlink) as the skill root.
Defensive patterns

Strategy: try-catch

When it happens

Trigger: Thrown at packages/adapter-utils/src/server-utils.ts:3161 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/0f6c7f7c663ac644. Report an issue: GitHub.

Appendix: source

Thrown at packages/adapter-utils/src/server-utils.ts:4405

): Promise<MaterializedPaperclipSkillCopyResult> {
  const sourceRoot = path.resolve(source);
  const targetRoot = path.resolve(target);
  const relativeTarget = path.relative(sourceRoot, targetRoot);
  const relativeSource = path.relative(targetRoot, sourceRoot);
  if (
    !relativeTarget ||
    (!relativeTarget.startsWith("..") && !path.isAbsolute(relativeTarget)) ||
    !relativeSource ||
    (!relativeSource.startsWith("..") && !path.isAbsolute(relativeSource))
  ) {
    throw new Error(
      "Refusing to materialize a skill into itself, an ancestor, or one of its descendants.",
    );
  }

  const rootStat = await fs.lstat(sourceRoot);
  if (rootStat.isSymbolicLink()) {
    throw new Error(
      "Refusing to materialize a skill root that is itself a symlink.",
    );
  }
  if (!rootStat.isDirectory()) {
    throw new Error("Paperclip skills must be directories.");
  }

  const result: MaterializedPaperclipSkillCopyResult = {
    copiedFiles: 0,
    skippedSymlinks: [],
  };

  const lockDir = `${targetRoot}.lock`;
  const releaseLock = await acquireMaterializeLock(lockDir);
  const tempRoot = `${targetRoot}.tmp-${process.pid}-${randomUUID()}`;

  async function copyEntry(
    sourcePath: string,

View on GitHub (pinned to 3f1d897a7c)