paperclipai/paperclip · error · Error
Refusing to use unsafe shim directory ${directoryPath}.
Error message
Refusing to use unsafe shim directory ${directoryPath}. What it means
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.
Source
Thrown at cli/src/install-store.ts:357
for (const entry of fs.readdirSync(sourceRoot)) {
if (entry.startsWith(".")) continue;
const candidate = path.join(sourceRoot, entry);
if (!retained.has(path.resolve(candidate))) {
fs.rmSync(candidate, { recursive: true, force: true });
removed.push(candidate);
}
}
}
return removed;
}
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;
}
}View on GitHub (pinned to 67001ec6eb)
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.
Example fix
// before: ~/.local/bin is a symlink // ls -la ~/.local/bin -> lrwxrwxrwx -> /shared/bin // after: real directory // rm ~/.local/bin && mkdir -p ~/.local/bin
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
import path from 'node:path';
function validateShimDirectories(shimPath: string): boolean {
const homeDir = path.dirname(path.dirname(path.dirname(shimPath)));
for (const dir of [homeDir, path.join(homeDir, '.local'), path.dirname(shimPath)]) {
if (!fs.existsSync(dir)) continue;
const stat = fs.lstatSync(dir);
if (!stat.isDirectory() || stat.isSymbolicLink()) return false;
}
return true;
}
// Call before assertManagedShimWritable / writeManagedShim:
if (!validateShimDirectories(paths.shimPath)) {
throw new Error('Shim directory is a symlink or non-directory; fix before installing.');
} Try / catch
try {
writeManagedShim(paths);
} catch (error) {
if (error instanceof Error && error.message.includes('unsafe shim directory')) {
console.error('Fix symlinked shim directories before retrying:', error.message);
}
throw error;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Refusing to activate payload that resolves outside ${paths.i
- Refusing to prune unsafe install-store path ${sourceRoot}.
- Refusing to replace non-regular shim ${paths.shimPath}.
- Refusing to replace multiply linked shim ${paths.shimPath}.
- Refusing to use non-directory install-store path ${directory
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/716d4f548043ef58.
Report an issue: GitHub.