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

  1. Inspect paths.currentPath: run 'ls -la <cliRoot>/current' to see what type of filesystem object occupies it.
  2. If it is a stale directory or file, remove it so the atomic swap can create a fresh symlink: 'rm -rf <cliRoot>/current'.
  3. Re-run the install or activation command that triggered flipCurrentAtomic.
  4. 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

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


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