paperclipai/paperclip · error · Error

Unexpected internal migrator dependency.

Error message

Unexpected internal migrator dependency.

What it means

The migrator bundle may contain exactly the internal packages @paperclipai/db and @paperclipai/shared at node_modules/@paperclipai/<name>. This error is thrown when the lockfile contains any other @paperclipai/* package entry — meaning the dependency graph pulled in an unexpected internal package (e.g. a transitive dep on another workspace package).

Solutions

  1. Remove the dependency on the extra @paperclipai/* package from db/shared, or vendor what it needed, then rebuild the bundle
  2. Check the generated lockfile before publishing: only db and shared entries may exist under node_modules/@paperclipai/
  3. If the dependency is intentional, extend the names list and manifest handling in scripts/cloud-migrator-artifacts.mjs deliberately, not ad hoc

Example fix

// before (db package.json)
"dependencies": { "@paperclipai/shared": "0.4.2", "@paperclipai/adapters": "0.4.2" }
// after (only allowlisted internal deps)
"dependencies": { "@paperclipai/shared": "0.4.2" }
Defensive patterns

Strategy: validation

Validate before calling

const allowed = new Set(["node_modules/@paperclipai/db", "node_modules/@paperclipai/shared"]);
for (const key of Object.keys(lock.packages ?? {})) {
  if (/(?:^|\/)node_modules\/@paperclipai\/[^/]+$/.test(key) && !allowed.has(key))
    throw new Error(`unexpected internal dep in lockfile: ${key}`);
}

Type guard

const onlyAllowedInternal = (lock, names = ["db", "shared"]) =>
  Object.keys(lock?.packages ?? {}).every((key) =>
    !/(?:^|\/)node_modules\/@paperclipai\/[^/]+$/.test(key) || names.some((n) => key === `node_modules/@paperclipai/${n}`));

Try / catch

try {
  assertLockfile(lock, manifest);
} catch (err) {
  if (err.message === "Unexpected internal migrator dependency.") throw new Error("db/shared gained a dependency on another @paperclipai package; remove it or extend the bundle allowlist");
  throw err;
}

Prevention

When it happens

Trigger: assertLockfile finds a key matching /node_modules\/@paperclipai\/[^/]+$/ that is not node_modules/@paperclipai/db or node_modules/@paperclipai/shared — for example @paperclipai/adapters appearing transitively.

Common situations: db or shared gained a dependency on another internal @paperclipai package; the install root package.json accidentally lists extra internal deps; lockfile was generated in the monorepo where workspaces resolved extra internal packages.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/a3818ca0d6945e4b. Report an issue: GitHub.

Appendix: source

Thrown at scripts/cloud-migrator-artifacts.mjs:52

  if (manifest?.version !== 1 || manifest.sourceSha !== sha || manifest.packageVersion !== versionFor(sha)) throw new Error("Artifact source identity mismatch.");
  for (const name of names) assertDescriptor(manifest.packages?.[name], "tgz");
  assertDescriptor(manifest.lockfile, "json");
}

export function assertLockfile(lock, manifest) {
  const version = manifest.packageVersion;
  if (lock?.lockfileVersion !== 3 || !lock.packages || Array.isArray(lock.packages) ||
      JSON.stringify(lock.packages[""]?.dependencies) !== JSON.stringify({ "@paperclipai/db": version })) throw new Error("Invalid migrator lockfile root.");
  for (const name of names) {
    const pin = lock.packages[`node_modules/@paperclipai/${name}`];
    const expected = manifest.packages[name];
    if (pin?.version !== version || pin.integrity !== expected.integrity || pin.resolved !== expected.url || pin.link || pin.inBundle) throw new Error("Migrator lockfile package pin mismatch.");
  }
  if (lock.packages["node_modules/@paperclipai/db"].dependencies?.["@paperclipai/shared"] !== version) throw new Error("Migrator shared dependency mismatch.");
  for (const [key, entry] of Object.entries(lock.packages)) {
    if (key === "") continue;
    if (!entry || typeof entry !== "object" || entry.link) throw new Error("Invalid migrator lockfile entry.");
    if (/(?:^|\/)node_modules\/@paperclipai\/[^/]+$/.test(key) && !names.some((name) => key === `node_modules/@paperclipai/${name}`)) throw new Error("Unexpected internal migrator dependency.");
    if (entry.inBundle === true) {
      if (!key.startsWith("node_modules/@paperclipai/db/node_modules/")) throw new Error("Unexpected bundled dependency.");
      continue;
    }
    if (!/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? "")) throw new Error("Migrator dependency has no strong integrity pin.");
    if (names.some((name) => key === `node_modules/@paperclipai/${name}`)) continue;
    const url = new URL(entry.resolved);
    if (url.origin !== "https://registry.npmjs.org" || url.username || url.password || url.search || url.hash) throw new Error("Migrator dependency must resolve to npm.");
  }
}

export function buildBundle(directory, sha, { exec = execFileSync } = {}) {
  versionFor(sha);
  directory = path.resolve(directory);
  const packages = {};
  for (const name of names) {
    const bytes = readFileSync(path.join(directory, `${name}.tgz`));
    assertMetadata(tarManifest(bytes), `@paperclipai/${name}`, sha);

View on GitHub (pinned to 3f1d897a7c)