paperclipai/paperclip · critical · Error
Refusing to activate non-directory payload ${payloadPath}.
Error message
Refusing to activate non-directory payload ${payloadPath}. What it means
Thrown by assertPayloadPath() when the payload path passed the in-store containment check but the entry at that path is not a real directory — it is a regular file or a symbolic link (lstatSync reports isSymbolicLink or !isDirectory). flipCurrentAtomic creates a directory symlink, so the target must be a real directory; a file or symlink payload could break activation or be a vector for confusion.
Source
Thrown at cli/src/install-store.ts:254
): void {
ensurePrivateDirectory(paths.cliRoot);
const temporaryPath = `${paths.manifestPath}.tmp-${process.pid}-${Date.now()}`;
try {
fs.writeFileSync(temporaryPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
fs.renameSync(temporaryPath, paths.manifestPath);
} finally {
fs.rmSync(temporaryPath, { force: true });
}
}
function assertPayloadPath(payloadPath: string, paths: InstallStorePaths): void {
const relative = path.relative(paths.installsRoot, path.resolve(payloadPath));
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
throw new Error(`Refusing to activate payload outside ${paths.installsRoot}.`);
}
const stat = fs.lstatSync(payloadPath);
if (!stat.isDirectory() || stat.isSymbolicLink()) {
throw new Error(`Refusing to activate non-directory payload ${payloadPath}.`);
}
const installsRealPath = fs.realpathSync(paths.installsRoot);
const payloadRealPath = fs.realpathSync(payloadPath);
if (!payloadRealPath.startsWith(`${installsRealPath}${path.sep}`)) {
throw new Error(`Refusing to activate payload that resolves outside ${paths.installsRoot}.`);
}
}
export function flipCurrentAtomic(
payloadPath: string,
paths = resolveInstallStorePaths(),
hooks: { beforeRename?: () => void } = {},
): void {
assertPayloadPath(payloadPath, paths);
ensurePrivateDirectory(paths.cliRoot);
try {
const currentStat = fs.lstatSync(paths.currentPath);
if (!currentStat.isSymbolicLink()) {View on GitHub (pinned to 67001ec6eb)
Solutions
- Inspect the payload path: 'ls -la <payloadPath>' and confirm it is a directory containing the unpacked package (node_modules/paperclipai/dist).
- If it is a file or symlink, remove it and reinstall the payload so it is unpacked as a real directory.
- If a symlink was intentionally placed, remove it and let the installer create a real payload directory.
- Verify the identifier passed to payloadPathFor matches an actually-unpacked payload, not a downloaded archive.
Example fix
$ ls -la ~/.paperclip/cli/installs/npm/1.2.3 -rw-r--r-- 1.2.3 (regular file, e.g. a tarball) $ rm ~/.paperclip/cli/installs/npm/1.2.3 $ paperclipai install # unpacks payload as real directory
Defensive patterns
Strategy: validation
Validate before calling
import fs from "node:fs";
function isRealPayloadDirectory(payloadPath: string): boolean {
try {
const st = fs.lstatSync(payloadPath);
return st.isDirectory() && !st.isSymbolicLink();
} catch { return false; }
}
// Before flipCurrentAtomic:
if (!isRealPayloadDirectory(payload)) throw new Error("payload is not a real directory"); 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 {
flipCurrentAtomic(payloadPath, paths);
} catch (err) {
if (err instanceof Error && err.message.startsWith("Refusing to activate non-directory payload")) {
// payload extraction failed; reinstall the payload
throw err;
}
throw err;
} Prevention
- Confirm the payload directory exists and is unpacked before activation.
- Verify install extraction completed (exit code) before calling flipCurrentAtomic.
- Never symlink the payload path; let the installer create a real directory.
- Validate the payload contains node_modules/paperclipai/dist before activating.
When it happens
Trigger: Called flipCurrentAtomic(payloadPath, paths) where payloadPath is inside installsRoot but fs.lstatSync(payloadPath) shows it is a file or a symlink rather than a real directory.
Common situations: 1) The install payload extraction failed, leaving a file (e.g. a tarball) instead of an unpacked directory. 2) A symlink was placed at the payload path. 3) The payload directory was replaced with a file after install. 4) Wrong identifier passed such that it resolves to a file artifact.
Related errors
- Refusing to use non-directory install-store path ${directory
- Refusing to modify path not owned by the current user: ${tar
- Refusing to use unsafe install-store marker ${paths.markerPa
- Refusing to use unrecognized install store ${paths.cliRoot}.
- Refusing to remove unsafe install-store path ${paths.cliRoot
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/26c5352700d4df47.
Report an issue: GitHub.