paperclipai/paperclip · error

ACPX provider package manifest must be an explicit normalize

Error message

ACPX provider package manifest must be an explicit normalized absolute path

What it means

createAcpxPackageJsonResolver also validates the manifest path (the provider package.json location, whether supplied explicitly or defaulted from root). It must be non-empty, absolute, null-byte-free, and already normalized (resolve(manifest) === manifest). This runs before realpathSync so the check fires on the literal path rather than on whatever the path symlinks to.

Source

Thrown at packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts:308

    !root ||
    !isAbsolute(root) ||
    root.includes("\0") ||
    resolve(root) !== root
  ) {
    throw new Error(
      "ACPX provider package root must be an explicit normalized absolute path",
    );
  }
  const manifest = (
    providerPackageManifest ?? resolve(root, "package.json")
  ).trim();
  if (
    !manifest ||
    !isAbsolute(manifest) ||
    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",
    );
  }

View on GitHub (pinned to 01ad858492)

Solutions

  1. Convert the manifest to an absolute normalized path: path.resolve(root, 'package.json') before passing it in
  2. If the manifest is configured, store it as an absolute path at config-load time
  3. Sanitize/reject paths containing NUL bytes earlier, when reading user input
  4. Add a pre-call assertion: isAbsolute(m) && resolve(m) === m && !m.includes('\0')

Example fix

// before
const resolver = createAcpxPackageJsonResolver(root, providerManifestPath); // './pkg/package.json'
// after
const manifest = path.resolve(root, providerManifestPath ?? 'package.json');
const resolver = createAcpxPackageJsonResolver(root, manifest);
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';
function normalizeManifest(root: string, manifest?: string): string {
  const m = path.resolve(root, manifest ?? 'package.json');
  if (m.includes('\0')) throw new Error('manifest path contains NUL byte');
  return m;
}

Type guard

function isValidManifestPath(p: unknown): p is string {
  return typeof p === 'string' && p.length > 0 && path.isAbsolute(p) && !p.includes('\0') && path.resolve(p) === p;
}

Try / catch

try {
  const resolver = createAcpxPackageJsonResolver(root, manifestPath);
} catch (err) {
  if (err instanceof Error && /manifest must be an explicit normalized absolute path/.test(err.message)) {
    manifestPath = path.resolve(root, manifestPath ?? 'package.json');
    return createAcpxPackageJsonResolver(root, manifestPath);
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing providerPackageManifest as a relative path (e.g. 'package.json' or './pkg/package.json'), an empty string, a path containing '\0', or a non-normalized path like '/opt/acpx/provider/../provider/package.json'.

Common situations: Constructing the manifest path by string concatenation without path.resolve; storing a relative manifest path in config; importing a provider spec file that records the manifest relative to its own directory.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/cb47a42e45f38b8c. Report an issue: GitHub.