paperclipai/paperclip · error
Refusing to replace non-symlink
Error message
Refusing to replace non-symlink ${paths.currentPath}. What it means
flipCurrentAtomic found an existing entry at the 'current' path that is not a symlink; it only ever atomically replaces a symlink and refuses to delete other file types.
Solutions
- Remove the non-symlink at ${paths.currentPath} manually after verifying it is safe, then retry.
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at cli/src/install-store.ts:273 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-08-18).
Data as JSON: /api/errors/85375cdf4c62551c.
Report an issue: GitHub.
Appendix: source
Thrown at cli/src/install-store.ts:273
}
const installsRealPath = fs.realpathSync(paths.installsRoot);
const payloadRealPath = fs.realpathSync(payloadPath);
if (!payloadRealPath.startsWith(`${installsRealPath}${path.sep}`)) {
throw new Error(`Refusing to activate payload that resolves outside ${paths.installsRoot}.`);
}
}
export function flipCurrentAtomic(
payloadPath: string,
paths = resolveInstallStorePaths(),
hooks: { beforeRename?: () => void } = {},
): void {
assertPayloadPath(payloadPath, paths);
ensurePrivateDirectory(paths.cliRoot);
try {
const currentStat = fs.lstatSync(paths.currentPath);
if (!currentStat.isSymbolicLink()) {
throw new Error(`Refusing to replace non-symlink ${paths.currentPath}.`);
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}
const temporaryLink = path.join(
paths.cliRoot,
`.current-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
);
const relativeTarget = path.relative(paths.cliRoot, payloadPath);
try {
fs.symlinkSync(relativeTarget, temporaryLink, "dir");
hooks.beforeRename?.();
fs.renameSync(temporaryLink, paths.currentPath);
} finally {
fs.rmSync(temporaryLink, { force: true });
}
}View on GitHub (pinned to 01ad858492)