paperclipai/paperclip · critical · Error
Refusing to remove install store with an invalid manifest at
Error message
Refusing to remove install store with an invalid manifest at ${paths.cliRoot}. What it means
Thrown by assertManagedInstallStore() when the manifest was read successfully but its payloadPath, resolved and made relative to installsRoot, either is empty, starts with '..' (escapes installsRoot), or is absolute. This is a path-confusion / path-traversal guard: a manifest pointing outside the install payloads directory could cause the removal logic to delete or act on arbitrary locations.
Source
Thrown at cli/src/install-store.ts:142
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}.`);
}
const manifest = readInstallManifest(paths);
if (!manifest) throw new Error(`Refusing to remove install store without a manifest at ${paths.cliRoot}.`);
const relativePayload = path.relative(paths.installsRoot, path.resolve(manifest.payloadPath));
if (!relativePayload || relativePayload.startsWith("..") || path.isAbsolute(relativePayload)) {
throw new Error(`Refusing to remove install store with an invalid manifest at ${paths.cliRoot}.`);
}
return manifest;
}
export async function withInstallStoreLock<T>(
callback: () => Promise<T>,
paths = resolveInstallStorePaths(),
options: { initialize?: boolean } = {},
): Promise<T> {
if (options.initialize !== false) initializeInstallStore(paths);
const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
const processIsAlive = (pid: number): boolean => {
try {
process.kill(pid, 0);
return true;
} catch (error) {
return (error as NodeJS.ErrnoException).code === "EPERM";
}View on GitHub (pinned to 67001ec6eb)
Solutions
- Inspect install.json and check manifest.payloadPath; it must be inside ~/.paperclip/cli/installs/<source>/<id>.
- If the store is corrupted, remove cliRoot and reinitialize with 'paperclipai install'.
- If the path is merely stale due to a moved store, correct payloadPath to the new in-store location or reinitialize.
- Never set payloadPath to an absolute or parent-relative path.
Example fix
// before: install.json has "payloadPath": "/usr/local/lib" $ rm -rf ~/.paperclip/cli $ paperclipai install # manifest gets correct in-store payloadPath
Defensive patterns
Strategy: validation
Validate before calling
import path from "node:path";
import fs from "node:fs";
import { resolveInstallStorePaths, readInstallManifest } from "./install-store.js";
function manifestPayloadInStore(paths = resolveInstallStorePaths()): boolean {
const m = readInstallManifest(paths);
if (!m) return false;
const rel = path.relative(paths.installsRoot, path.resolve(m.payloadPath));
return !!rel && !rel.startsWith("..") && !path.isAbsolute(rel);
} Type guard
import path from "node:path";
function isWithin(installsRoot: string, payloadPath: string): boolean {
const rel = path.relative(installsRoot, path.resolve(payloadPath));
return !!rel && !rel.startsWith("..") && !path.isAbsolute(rel);
} Try / catch
try {
assertManagedInstallStore(paths);
} catch (err) {
if (err instanceof Error && err.message.includes("invalid manifest")) {
console.error("Manifest payloadPath escapes installsRoot. Reset store and reinstall.");
process.exit(1);
}
throw err;
} Prevention
- Never hand-edit install.json's payloadPath to an external location.
- If moving the store, reinitialize rather than editing paths in the manifest.
- Treat a manifest with an external payloadPath as tampering.
When it happens
Trigger: Called assertManagedInstallStore() where readInstallManifest returns a manifest whose payloadPath resolves to a location outside paths.installsRoot (e.g. '/etc', '../../..', or an absolute path).
Common situations: 1) Manifest was hand-edited to point payloadPath at an external directory. 2) installsRoot was moved/renamed but the manifest still holds the old absolute path. 3) Tampering to trick prune/remove into touching files outside the store. 4) A bug in an older version wrote a bad payloadPath.
Related errors
- Refusing to activate payload outside ${paths.installsRoot}.
- Refusing to activate payload that resolves outside ${paths.i
- Refusing to write export file outside output directory: ${re
- Refusing to use non-directory install-store path ${directory
- 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/ffb0db15ff1c6770.
Report an issue: GitHub.