paperclipai/paperclip · error · Error
Invalid migrator lockfile root.
Error message
Invalid migrator lockfile root.
What it means
The cloud migrator artifacts script throws this from assertLockfile when the lockfile root is structurally wrong: lockfileVersion is not 3, packages is missing or is an array, or the root package "" does not have dependencies exactly { "@paperclipai/db": <version> }. It guarantees the lockfile being installed is the generated migrator lockfile and nothing else.
Solutions
- Use the generated migrator lockfile (lockfileVersion 3) from the artifact set, not an arbitrary package-lock.json.
- Regenerate the lockfile artifacts so the root depends exactly on @paperclipai/db at manifest.packageVersion.
- Check that the lockfile was not modified after download (npm install can rewrite it).
- Confirm manifest and lockfile come from the same artifact release.
Example fix
// before (rewritten root)
"packages": { "": { "dependencies": { "@paperclipai/db": "0.0.1", "extra": "..." } } }
// after
"packages": { "": { "dependencies": { "@paperclipai/db": "<manifest.packageVersion>" } } } Defensive patterns
Strategy: validation
Validate before calling
const root = lock?.packages?.['']?.dependencies;
const isMigratorLock = lock?.lockfileVersion === 3 && lock.packages && !Array.isArray(lock.packages) &&
JSON.stringify(root) === JSON.stringify({ '@paperclipai/db': manifest.packageVersion });
if (!isMigratorLock) throw new Error('Not the generated migrator lockfile'); Type guard
const isMigratorLockfile = (lock) => lock?.lockfileVersion === 3 && typeof lock.packages === 'object' && !Array.isArray(lock.packages);
Try / catch
try { assertLockfile(lock, manifest); } catch (e) {
if (e.message === 'Invalid migrator lockfile root.') {
console.error('Use the lockfile shipped in the artifact set, not a project package-lock.json');
} else throw e;
} Prevention
- Only install from the lockfile downloaded as part of the verified artifact set.
- Run installs with frozen lockfile settings so tooling cannot rewrite the root dependencies.
- Verify manifest and lockfile come from the same release before asserting.
When it happens
Trigger: assertLockfile receives a lock object that is null, was produced by a different package manager/lockfileVersion (not 3), has packages as an array, or whose root package dependencies differ from the single expected @paperclipai/db dependency pinned to manifest.packageVersion.
Common situations: Pointing the migrator at a project's own package-lock.json instead of the generated migrator lockfile; a regenerated lockfile from a different manifest version; npm rewriting the root dependencies section.
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
- Unexpected bundled dependency.
- ACPX provider package name is invalid
- Artifact source identity mismatch.
- Invalid migrator lockfile entry.
- Migrator lockfile package pin mismatch.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/b6c32c2a7b586db5.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/cloud-migrator-artifacts.mjs:42
function assertDescriptor(pin, extension) {
if (!pin || typeof pin.integrity !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(pin.integrity) ||
!Number.isSafeInteger(pin.size) || pin.size <= 0 || pin.size > maximumBytes) throw new Error("Invalid artifact integrity or size.");
const digest = Buffer.from(pin.integrity.slice(7), "base64");
if (digest.toString("base64") !== pin.integrity.slice(7) || pin.url !== `${artifactBase}/blobs/${digest.toString("hex")}.${extension}`) {
throw new Error("Artifact URL does not match its content hash and trusted origin.");
}
}
export function assertManifest(manifest, sha) {
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.");View on GitHub (pinned to 3f1d897a7c)