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

  1. Inspect install.json and check manifest.payloadPath; it must be inside ~/.paperclip/cli/installs/<source>/<id>.
  2. If the store is corrupted, remove cliRoot and reinitialize with 'paperclipai install'.
  3. If the path is merely stale due to a moved store, correct payloadPath to the new in-store location or reinitialize.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/ffb0db15ff1c6770. Report an issue: GitHub.