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
- 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)