paperclipai/paperclip · error · Error
Refusing to replace non-symlink ${paths.currentPath}.
Error message
Refusing to replace non-symlink ${paths.currentPath}. What it means
Thrown by flipCurrentAtomic when paths.currentPath exists but is not a symbolic link. The atomic-swap strategy creates a temporary symlink and renames it over 'current'; this guard ensures 'current' was never replaced by a regular file or directory behind the installer's back, preventing data loss or clobbering of a non-symlink artifact.
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 67001ec6eb)
Solutions
- Inspect paths.currentPath: run 'ls -la <cliRoot>/current' to see what type of filesystem object occupies it.
- If it is a stale directory or file, remove it so the atomic swap can create a fresh symlink: 'rm -rf <cliRoot>/current'.
- Re-run the install or activation command that triggered flipCurrentAtomic.
- If 'current' should legitimately be a directory, review your install workflow—it must always be managed as a symlink by this code.
Example fix
// before: current is a real directory
// ls -la ~/.paperclip/cli/current -> drwxr-xr-x
// after: remove it so flipCurrentAtomic can create the symlink
fs.rmSync(paths.currentPath, { recursive: true, force: true });
// now re-run activation Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
function ensureCurrentIsSymlinkOrAbsent(currentPath: string): void {
try {
const stat = fs.lstatSync(currentPath);
if (!stat.isSymbolicLink()) {
fs.rmSync(currentPath, { recursive: true, force: true });
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
}
}
// Call before flipCurrentAtomic:
ensureCurrentIsSymlinkOrAbsent(paths.currentPath); Try / catch
try {
flipCurrentAtomic(payloadPath, paths);
} catch (error) {
if (error instanceof Error && error.message.includes('Refusing to replace non-symlink')) {
// 'current' was clobbered with a file/dir; decide whether to remove it
console.error('current path is not a symlink:', paths.currentPath);
}
throw error;
} Prevention
- Never manually create files or directories at paths.currentPath (~/.<paperclip-home>/cli/current).
- The 'current' path must always be a symlink managed exclusively by flipCurrentAtomic.
- If cleaning up a broken install, remove the entire cliRoot rather than just 'current'.
When it happens
Trigger: Calling flipCurrentAtomic after something has replaced paths.currentPath (the 'current' symlink under cliRoot) with a regular file or a real directory. This can happen if a user manually created a file/directory named 'current' or if a different install mechanism wrote into that path.
Common situations: Manual filesystem manipulation inside ~/.<paperclip-home>/cli/, a broken previous install that left a directory instead of a symlink, or a user copied a directory into 'current' expecting it to be the active install.
Related errors
- Refusing to activate payload that resolves outside ${paths.i
- Refusing to prune unsafe install-store path ${sourceRoot}.
- Refusing to use unsafe shim directory ${directoryPath}.
- Refusing to use non-directory install-store path ${directory
- Refusing to modify path not owned by the current user: ${tar
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/85375cdf4c62551c.
Report an issue: GitHub.