paperclipai/paperclip · error
ACPX provider node_modules resolves outside the selected pro
Error message
ACPX provider node_modules resolves outside the selected provider root
What it means
The resolver canonicalizes <canonicalRoot>/node_modules and requires the result to remain inside the canonical provider root. If node_modules is a symlink to an external directory (e.g. a shared pnpm store), dependency resolution would read packages from outside the vetted provider installation, so the library rejects the layout to keep dependency provenance auditable.
Source
Thrown at packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts:323
manifest.includes("\0") ||
resolve(manifest) !== manifest
) {
throw new Error(
"ACPX provider package manifest must be an explicit normalized absolute path",
);
}
const canonicalRoot = realpathSync(root);
const canonicalManifest = realpathSync(manifest);
if (!pathIsInside(canonicalRoot, canonicalManifest)) {
throw new Error(
"ACPX provider package manifest resolves outside the selected provider root",
);
}
const canonicalNodeModules = realpathSync(
resolve(canonicalRoot, "node_modules"),
);
if (!pathIsInside(canonicalRoot, canonicalNodeModules)) {
throw new Error(
"ACPX provider node_modules resolves outside the selected provider root",
);
}
return (packageName, issuerPackageJsonPath) => {
const canonicalIssuer =
issuerPackageJsonPath === undefined
? canonicalManifest
: realpathSync(issuerPackageJsonPath);
if (!pathIsInside(canonicalRoot, canonicalIssuer)) {
throw new Error(
`ACPX provider package issuer for ${packageName} resolves outside the selected provider root`,
);
}
const packageJsonPath = realpathSync(
resolvePackageJsonFromIssuer(packageName, canonicalIssuer),
);
if (!pathIsInside(canonicalNodeModules, packageJsonPath)) {
throw new Error(View on GitHub (pinned to 01ad858492)
Solutions
- Install provider dependencies so node_modules is a real directory inside the provider root (npm/yarn install directly in the provider package, or copy the tree into root)
- Use pnpm's node-linker=hoisted in .npmrc for the provider install to avoid symlinked layouts
- Point the provider root at the directory whose realpath actually contains node_modules
- In containers, mount/copy node_modules inside the provider root rather than linking from outside
Example fix
// before ln -s /store/acpx-provider/node_modules /opt/acpx/provider/node_modules // after # .npmrc in provider root node-linker=hoisted $ npm install --prefix /opt/acpx/provider # real node_modules inside root
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
import path from 'node:path';
function nodeModulesInsideRoot(root: string): boolean {
const canonicalRoot = fs.realpathSync(root);
const nm = fs.realpathSync(path.resolve(canonicalRoot, 'node_modules'));
return nm.startsWith(canonicalRoot + path.sep);
} Try / catch
try {
const resolver = createAcpxPackageJsonResolver(root);
} catch (err) {
if (err instanceof Error && /node_modules resolves outside the selected provider root/.test(err.message)) {
throw new Error(`Provider ${root} uses a symlinked node_modules store; reinstall with node-linker=hoisted or copy node_modules into the provider root`);
}
throw err;
} Prevention
- Prefer npm/yarn installs for provider packages; if pnpm, set node-linker=hoisted in the provider's .npmrc
- Check a staged provider install for escaping symlinks (find root -type l) before registering it
- In containers, copy node_modules into the provider root instead of bind-mounting from outside
- Document that the provider root must be a self-contained real directory
When it happens
Trigger: Provider root contains node_modules as a symlink pointing outside root, so realpathSync(resolve(canonicalRoot, 'node_modules')) escapes canonicalRoot; ancestor dependency resolution would then walk an unvetted store path.
Common situations: Installing the provider with pnpm (which symlinks node_modules/.pnpm and may link node_modules itself); a monorepo hoisting node_modules to the repo root while root is a subpackage; docker/volume setups that relocate node_modules outside the provider directory.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- ACPX provider package manifest resolves outside the selected
- ACPX ${agent} runtime executable must be a real regular file
- Access denied
- Trusted viewer must not use symlinks
- ACPX provider package root must be an explicit normalized ab
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/ca0489b84f0f685e.
Report an issue: GitHub.