paperclipai/paperclip · critical · Error

Artifact bytes do not match their immutable pin.

Error message

Artifact bytes do not match their immutable pin.

What it means

verifyBytes compares downloaded or on-disk artifact bytes against the manifest's immutable pin (exact size and sha512 integrity). This error means the bytes diverge from what the manifest pins — the file was corrupted, truncated, replaced, or the manifest describes a different build. It is the content-addressing integrity gate for the whole bundle.

Solutions

  1. Re-download or re-fetch the bundle fresh (rm the local artifacts and re-run the build or verify) so bytes and manifest come from the same run
  2. Rebuild the bundle with `node scripts/cloud-migrator-artifacts.mjs build <dir> <sha>` so manifest pins match current bytes
  3. Confirm the CDN/S3 object was not overwritten (uploads use --if-none-match * for immutability); if it was, treat the artifact as compromised and re-publish under the correct hash
  4. Check the directory contains files from a single build (manifest.json, package-lock.json, db.tgz, shared.tgz all same SHA)

Example fix

// before (file edited after manifest written)
verifyBytes(bytes, pin) -> sha512(actual) != pin.integrity
// after (rebuild so pins match bytes)
node scripts/cloud-migrator-artifacts.mjs build ./bundle <sha>
node scripts/cloud-migrator-artifacts.mjs validate ./bundle <sha>
Defensive patterns

Strategy: validation

Validate before calling

import { createHash } from "node:crypto";
const integrityFor = (b) => `sha512-${createHash("sha512").update(b).digest("base64")}`;
const bytes = readFileSync(path.join(dir, "db.tgz"));
const pin = manifest.packages.db;
if (bytes.length !== pin.size || integrityFor(bytes) !== pin.integrity) throw new Error("db.tgz does not match manifest pin");

Type guard

const matchesPin = (bytes, pin) =>
  Buffer.isBuffer(bytes) && bytes.length === pin.size && integrityFor(bytes) === pin.integrity;

Try / catch

try {
  validateBundle(dir, sha);
} catch (err) {
  if (err.message === "Artifact bytes do not match their immutable pin.") {
    // bytes and manifest disagree: rebuild or re-download from scratch
    rmSync(path.join(dir, "db.tgz"));
    buildBundle(dir, sha); // or re-run verifyPublished to re-download
  } else throw err;
}

Prevention

When it happens

Trigger: verifyBytes(bytes, pin) is called from validateBundle (local files) or verifyPublished (downloads) and either bytes.length !== pin.size or integrityFor(bytes) !== pin.integrity.

Common situations: A partially failed download or truncated tgz on disk; CDN serving a stale cached blob under an address that was overwritten; editing a tgz/json after the manifest was written; mixing files from two different builds of the same SHA.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

    writeFileSync(path.join(scratch, "package.json"), JSON.stringify(root));
    exec("npm", ["install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund", "--registry=https://registry.npmjs.org"], { cwd: scratch, stdio: "inherit", timeout: 180_000 });
    const lock = JSON.parse(readFileSync(path.join(scratch, "package-lock.json"), "utf8"));
    // Both new packages are local during resolution. npm ci subsequently uses
    // these immutable URLs, without looking up the new npm versions.
    lock.packages[""].dependencies = { "@paperclipai/db": versionFor(sha) };
    for (const name of names) lock.packages[`node_modules/@paperclipai/${name}`].resolved = packages[name].url;
    const lockBytes = Buffer.from(JSON.stringify(lock) + "\n");
    const manifest = { version: 1, sourceSha: sha, packageVersion: versionFor(sha), packages, lockfile: descriptor(lockBytes, "json") };
    assertManifest(manifest, sha);
    assertLockfile(lock, manifest);
    writeFileSync(path.join(directory, "package-lock.json"), lockBytes);
    writeFileSync(path.join(directory, "manifest.json"), JSON.stringify(manifest) + "\n");
    return manifest;
  } finally { rmSync(scratch, { recursive: true, force: true }); }
}

function verifyBytes(bytes, pin) {
  if (bytes.length !== pin.size || integrityFor(bytes) !== pin.integrity) throw new Error("Artifact bytes do not match their immutable pin.");
}

export function validateBundle(directory, sha) {
  const manifest = JSON.parse(readFileSync(path.join(directory, "manifest.json"), "utf8"));
  assertManifest(manifest, sha);
  for (const name of names) {
    const bytes = readFileSync(path.join(directory, `${name}.tgz`));
    verifyBytes(bytes, manifest.packages[name]);
    assertMetadata(tarManifest(bytes), `@paperclipai/${name}`, sha);
  }
  const bytes = readFileSync(path.join(directory, "package-lock.json"));
  verifyBytes(bytes, manifest.lockfile);
  assertLockfile(JSON.parse(bytes), manifest);
  return manifest;
}

/** Exercise the real dependency graph before publishing, with no new npm versions. */
export function verifyInstall(directory, sha, { exec = execFileSync } = {}) {

View on GitHub (pinned to 3f1d897a7c)