{"record":{"id":"ebb89e170610f759","repo":"paperclipai/paperclip","slug":"refusing-to-replace-multiply-linked-shim-paths-s","errorCode":null,"errorMessage":"Refusing to replace multiply linked shim ${paths.shimPath}.","messagePattern":"Refusing to replace multiply linked shim (.+?)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/install-store.ts","lineNumber":367,"sourceCode":"}\n\nexport function assertManagedShimWritable(paths = resolveInstallStorePaths()): void {\n  const homeDir = path.dirname(path.dirname(path.dirname(paths.shimPath)));\n  for (const directoryPath of [homeDir, path.join(homeDir, \".local\"), path.dirname(paths.shimPath)]) {\n    if (!fs.existsSync(directoryPath)) continue;\n    const directoryStat = fs.lstatSync(directoryPath);\n    if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {\n      throw new Error(`Refusing to use unsafe shim directory ${directoryPath}.`);\n    }\n    assertOwnedByCurrentUser(directoryStat, directoryPath);\n  }\n  try {\n    const stat = fs.lstatSync(paths.shimPath);\n    if (!stat.isFile() || stat.isSymbolicLink()) {\n      throw new Error(`Refusing to replace non-regular shim ${paths.shimPath}.`);\n    }\n    assertOwnedByCurrentUser(stat, paths.shimPath);\n    if (stat.nlink > 1) throw new Error(`Refusing to replace multiply linked shim ${paths.shimPath}.`);\n    const existing = fs.readFileSync(paths.shimPath, \"utf8\");\n    if (!isManagedShimContents(existing)) {\n      throw new Error(`Refusing to replace existing non-managed command ${paths.shimPath}.`);\n    }\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n  }\n}\n\nfunction shellQuote(value: string): string {\n  return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nfunction isManagedShimContents(contents: string): boolean {\n  const lines = contents.split(\"\\n\");\n  return (\n    lines.length === 5 &&\n    lines[0] === \"#!/bin/sh\" &&","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/install-store.ts#L349-L385","documentation":"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.","triggerScenarios":"Calling assertManagedShimWritable or writeManagedShim when ~/.local/bin/paperclipai has been hard-linked (e.g., via 'ln paperclipai paperclipai-backup'), making nlink > 1.","commonSituations":"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.","solutions":["Find the hard links: 'find ~/.local/bin -inum $(stat -c %i ~/.local/bin/paperclipai)'.","Remove the extra hard links so nlink returns to 1: 'rm <extra-link-path>'.","Verify nlink is now 1 with 'stat -c %h ~/.local/bin/paperclipai'.","Re-run the install command."],"exampleFix":"// before: shim has nlink > 1\n// ln ~/.local/bin/paperclipai ~/.local/bin/paperclip-old\n// stat -c %h ~/.local/bin/paperclipai -> 2\n\n// after: remove extra hard link\n// rm ~/.local/bin/paperclip-old\n// stat -c %h ~/.local/bin/paperclipai -> 1","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\n\nfunction isShimSinglyLinked(shimPath: string): boolean {\n  try {\n    const stat = fs.lstatSync(shimPath);\n    return stat.isFile() && !stat.isSymbolicLink() && stat.nlink <= 1;\n  } catch (error) {\n    return (error as NodeJS.ErrnoException).code === 'ENOENT';\n  }\n}\n\n// Call before writeManagedShim:\nif (!isShimSinglyLinked(paths.shimPath)) {\n  console.warn('Shim is multiply linked; removing extra links before install.');\n  // Find and remove extra links, or just delete and recreate\n  fs.rmSync(paths.shimPath, { force: true });\n}","typeGuard":null,"tryCatchPattern":"try {\n  writeManagedShim(paths);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('multiply linked shim')) {\n    // Find extra hard links and remove them\n    console.error('Shim has nlink > 1; find extra links with: find ~ -inum $(stat -c %i %s)', paths.shimPath);\n  }\n  throw error;\n}","preventionTips":["Never create hard links to the managed shim file.","If backing up the shim, copy it (cp) rather than hard-linking it (ln without -s).","Check 'stat -c %h ~/.local/bin/paperclipai' before install; it should be 1 or ENOENT."],"tags":["security","install-store","shim","hardlink","cli"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}