{"record":{"id":"716d4f548043ef58","repo":"paperclipai/paperclip","slug":"refusing-to-use-unsafe-shim-directory-directoryp","errorCode":null,"errorMessage":"Refusing to use unsafe shim directory ${directoryPath}.","messagePattern":"Refusing to use unsafe shim directory (.+?)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/install-store.ts","lineNumber":357,"sourceCode":"    for (const entry of fs.readdirSync(sourceRoot)) {\n      if (entry.startsWith(\".\")) continue;\n      const candidate = path.join(sourceRoot, entry);\n      if (!retained.has(path.resolve(candidate))) {\n        fs.rmSync(candidate, { recursive: true, force: true });\n        removed.push(candidate);\n      }\n    }\n  }\n  return removed;\n}\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}","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/install-store.ts#L339-L375","documentation":"Thrown by assertManagedShimWritable when one of the three shim-adjacent directories ($HOME, $HOME/.local, or dirname(shimPath) i.e. $HOME/.local/bin) exists but is not a real directory or is a symlink. The shim at ~/.local/bin/paperclipai is the user-facing entry point, so these directories must be real, user-owned directories to prevent symlink-based privilege escalation or path interception.","triggerScenarios":"Calling assertManagedShimWritable (directly or via writeManagedShim) when $HOME, $HOME/.local, or $HOME/.local/bin is a symlink, regular file, or other non-directory filesystem object.","commonSituations":"HOME is set to a symlinked path (common in dotfile-management setups). $HOME/.local is symlinked to another volume or a dotfile-managed location. A user replaced .local/bin with a symlink to /usr/local/bin or a shared bin directory.","solutions":["Check each of the three directories with 'ls -la' to identify which one is a symlink or non-directory.","Replace the symlink with a real directory: remove the symlink, create a real directory with mkdir -p.","If you intentionally use symlinked home directories, reconfigure the shim path via PAPERCLIP_SHIM_PATH or paperclipHome so it resolves through real directories.","Verify ownership: the code also calls assertOwnedByCurrentUser, so ensure the directory is owned by your uid."],"exampleFix":"// before: ~/.local/bin is a symlink\n// ls -la ~/.local/bin -> lrwxrwxrwx -> /shared/bin\n\n// after: real directory\n// rm ~/.local/bin && mkdir -p ~/.local/bin","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nimport path from 'node:path';\n\nfunction validateShimDirectories(shimPath: string): boolean {\n  const homeDir = path.dirname(path.dirname(path.dirname(shimPath)));\n  for (const dir of [homeDir, path.join(homeDir, '.local'), path.dirname(shimPath)]) {\n    if (!fs.existsSync(dir)) continue;\n    const stat = fs.lstatSync(dir);\n    if (!stat.isDirectory() || stat.isSymbolicLink()) return false;\n  }\n  return true;\n}\n\n// Call before assertManagedShimWritable / writeManagedShim:\nif (!validateShimDirectories(paths.shimPath)) {\n  throw new Error('Shim directory is a symlink or non-directory; fix before installing.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  writeManagedShim(paths);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('unsafe shim directory')) {\n    console.error('Fix symlinked shim directories before retrying:', error.message);\n  }\n  throw error;\n}","preventionTips":["Ensure $HOME, $HOME/.local, and $HOME/.local/bin are real directories, not symlinks.","If using a dotfile manager, configure it to manage the contents of .local/bin, not the directory itself via symlink.","Set PAPERCLIP_SHIM_PATH to a path that resolves through real directories if HOME is symlinked."],"tags":["security","install-store","shim","symlink","cli"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}