paperclipai/paperclip · error · Error

Refusing to replace multiply linked shim ${paths.shimPath}.

Error message

Refusing to replace multiply linked shim ${paths.shimPath}.

What it means

Thrown by assertManagedShimWritable when paths.shimPath is a regular file but has a hard link count (stat.nlink) greater than 1. A multi-linked file means other directory entries point to the same inode; overwriting it via atomic rename would silently change content visible through those other names. The installer refuses to replace such files to prevent unintended side effects on linked paths.

Source

Thrown at cli/src/install-store.ts:367

}

export function assertManagedShimWritable(paths = resolveInstallStorePaths()): void {
  const homeDir = path.dirname(path.dirname(path.dirname(paths.shimPath)));
  for (const directoryPath of [homeDir, path.join(homeDir, ".local"), path.dirname(paths.shimPath)]) {
    if (!fs.existsSync(directoryPath)) continue;
    const directoryStat = fs.lstatSync(directoryPath);
    if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
      throw new Error(`Refusing to use unsafe shim directory ${directoryPath}.`);
    }
    assertOwnedByCurrentUser(directoryStat, directoryPath);
  }
  try {
    const stat = fs.lstatSync(paths.shimPath);
    if (!stat.isFile() || stat.isSymbolicLink()) {
      throw new Error(`Refusing to replace non-regular shim ${paths.shimPath}.`);
    }
    assertOwnedByCurrentUser(stat, paths.shimPath);
    if (stat.nlink > 1) throw new Error(`Refusing to replace multiply linked shim ${paths.shimPath}.`);
    const existing = fs.readFileSync(paths.shimPath, "utf8");
    if (!isManagedShimContents(existing)) {
      throw new Error(`Refusing to replace existing non-managed command ${paths.shimPath}.`);
    }
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
  }
}

function shellQuote(value: string): string {
  return `'${value.replace(/'/g, `'"'"'`)}'`;
}

function isManagedShimContents(contents: string): boolean {
  const lines = contents.split("\n");
  return (
    lines.length === 5 &&
    lines[0] === "#!/bin/sh" &&

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Find the hard links: 'find ~/.local/bin -inum $(stat -c %i ~/.local/bin/paperclipai)'.
  2. Remove the extra hard links so nlink returns to 1: 'rm <extra-link-path>'.
  3. Verify nlink is now 1 with 'stat -c %h ~/.local/bin/paperclipai'.
  4. Re-run the install command.

Example fix

// before: shim has nlink > 1
// ln ~/.local/bin/paperclipai ~/.local/bin/paperclip-old
// stat -c %h ~/.local/bin/paperclipai -> 2

// after: remove extra hard link
// rm ~/.local/bin/paperclip-old
// stat -c %h ~/.local/bin/paperclipai -> 1
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';

function isShimSinglyLinked(shimPath: string): boolean {
  try {
    const stat = fs.lstatSync(shimPath);
    return stat.isFile() && !stat.isSymbolicLink() && stat.nlink <= 1;
  } catch (error) {
    return (error as NodeJS.ErrnoException).code === 'ENOENT';
  }
}

// Call before writeManagedShim:
if (!isShimSinglyLinked(paths.shimPath)) {
  console.warn('Shim is multiply linked; removing extra links before install.');
  // Find and remove extra links, or just delete and recreate
  fs.rmSync(paths.shimPath, { force: true });
}

Try / catch

try {
  writeManagedShim(paths);
} catch (error) {
  if (error instanceof Error && error.message.includes('multiply linked shim')) {
    // Find extra hard links and remove them
    console.error('Shim has nlink > 1; find extra links with: find ~ -inum $(stat -c %i %s)', paths.shimPath);
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling assertManagedShimWritable or writeManagedShim when ~/.local/bin/paperclipai has been hard-linked (e.g., via 'ln paperclipai paperclipai-backup'), making nlink > 1.

Common situations: A backup tool or dotfile manager created a hard link to the shim. A user manually hard-linked the binary to another name. A filesystem snapshot or copy created additional hard links.

Related errors


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