paperclipai/paperclip · critical · Error
Refusing to remove unsafe install-store path ${paths.cliRoot
Error message
Refusing to remove unsafe install-store path ${paths.cliRoot}. What it means
Thrown by assertManagedInstallStore() at the very first check: cliRoot is examined with lstatSync and found to be either not a directory or a symlink. This function is the gate before destructive removal operations, so it refuses to act on a path that is not a real directory it controls, preventing accidental deletion of arbitrary targets via symlink redirection.
Source
Thrown at cli/src/install-store.ts:119
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
try {
fs.writeFileSync(paths.markerPath, MANAGED_STORE_MARKER, { mode: 0o600, flag: "wx" });
} catch (writeError) {
if (
(writeError as NodeJS.ErrnoException).code !== "EEXIST" ||
fs.readFileSync(paths.markerPath, "utf8") !== MANAGED_STORE_MARKER
) {
throw writeError;
}
}
}
}
export function assertManagedInstallStore(paths = resolveInstallStorePaths()): InstallManifest {
const cliStat = fs.lstatSync(paths.cliRoot);
if (!cliStat.isDirectory() || cliStat.isSymbolicLink()) {
throw new Error(`Refusing to remove unsafe install-store path ${paths.cliRoot}.`);
}
assertOwnedByCurrentUser(cliStat, paths.cliRoot);
let markerStat: fs.Stats;
try {
markerStat = fs.lstatSync(paths.markerPath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
throw new Error(`Refusing to remove unverified install store ${paths.cliRoot}.`);
}
throw error;
}
if (!markerStat.isFile() || markerStat.isSymbolicLink() || markerStat.nlink > 1) {
throw new Error(`Refusing to remove unverified install store ${paths.cliRoot}.`);
}
assertOwnedByCurrentUser(markerStat, paths.markerPath);
if (fs.readFileSync(paths.markerPath, "utf8") !== MANAGED_STORE_MARKER) {
throw new Error(`Refusing to remove unverified install store ${paths.cliRoot}.`);
}View on GitHub (pinned to 67001ec6eb)
Solutions
- Inspect the entry: 'ls -la ~/.paperclip/cli'.
- If it is a symlink or stray file you control, remove it so the store can be re-initialized, or point PAPERCLIP_HOME elsewhere.
- If the symlink is unexpected, investigate before removing — do not blindly delete.
- Once the path is clear, re-run the CLI operation to recreate a genuine store.
Example fix
$ ls -la ~/.paperclip/cli lrwxrwxrwx cli -> /some/other/dir $ rm ~/.paperclip/cli $ paperclipai install
Defensive patterns
Strategy: validation
Validate before calling
import fs from "node:fs";
import { resolveInstallStorePaths } from "./install-store.js";
function isRealStoreRoot(paths = resolveInstallStorePaths()): boolean {
try {
const st = fs.lstatSync(paths.cliRoot);
return st.isDirectory() && !st.isSymbolicLink();
} catch { return false; }
} Type guard
import fs from "node:fs";
function isRealDirectory(p: string): boolean {
const st = fs.lstatSync(p);
return st.isDirectory() && !st.isSymbolicLink();
} Try / catch
try {
assertManagedInstallStore(paths);
} catch (err) {
if (err instanceof Error && err.message.includes("unsafe install-store path")) {
console.error(`${paths.cliRoot} is not a real directory; refusing destructive op.`);
process.exit(1);
}
throw err;
} Prevention
- Never replace ~/.paperclip/cli with a symlink.
- Before uninstall, verify the path type manually if anything looks off.
- Investigate unexpected symlinks before removing them.
When it happens
Trigger: Called assertManagedInstallStore() (typically before uninstall/prune/remove flows) where paths.cliRoot is a regular file or a symbolic link rather than a real directory.
Common situations: 1) A symlink was placed at ~/.paperclip/cli. 2) A prior operation left a file at that path. 3) PAPERCLIP_HOME resolves to a location where 'cli' is a symlink or file. 4) Tampering attempt to redirect removal at another directory.
Related errors
- Refusing to use non-directory install-store path ${directory
- Refusing to use unsafe install-store marker ${paths.markerPa
- Refusing to remove unverified install store ${paths.cliRoot}
- Refusing to activate payload outside ${paths.installsRoot}.
- 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/6138b0623f9e0fe0.
Report an issue: GitHub.