paperclipai/paperclip · error · Error

Artifact exceeds size limit.

Error message

Artifact exceeds size limit.

What it means

download() streams the response body and enforces a hard cap of 32 MiB (maximumBytes) accumulated before buffering the artifact. This error is thrown as soon as the running size exceeds the cap, protecting against a malicious or misconfigured endpoint returning an enormous payload. The reader is cancelled in the finally block.

Solutions

  1. Verify the URL matches the expected content-addressed form `${artifactBase}/blobs/<sha512hex>.<ext>` before fetching
  2. Re-fetch the manifest from `${artifactBase}/<sha>/manifest.json` and re-run verifyPublished so pins and URLs come from the trusted manifest
  3. If a legitimate artifact genuinely outgrew 32 MiB, raise maximumBytes deliberately in scripts/cloud-migrator-artifacts.mjs after confirming the blob's real size

Example fix

// before (wrong URL, unbounded response)
const bytes = await download("https://example.com/huge-dump.tgz", fetch);
// after (fetch the pinned content-addressed blob)
const manifest = JSON.parse(await download(`${artifactBase}/${sha}/manifest.json`, fetch));
const bytes = await download(manifest.packages.db.url, fetch); // asserted <= 32MiB by assertDescriptor
Defensive patterns

Strategy: try-catch

Validate before calling

const head = await fetch(url, { method: "HEAD" });
const size = Number(head.headers.get("content-length") ?? 0);
if (size > 32 * 1024 * 1024) throw new Error(`artifact too large: ${size} bytes`);

Type guard

const withinArtifactLimit = (res) =>
  Number(res.headers?.get?.("content-length") ?? 0) <= 32 * 1024 * 1024;

Try / catch

try {
  const bytes = await download(url, fetch);
} catch (err) {
  if (err.message === "Artifact exceeds size limit.") {
    // wrong or hostile URL; re-resolve from the trusted manifest pins
    const manifest = JSON.parse(await download(`${artifactBase}/${sha}/manifest.json`, fetch));
    url = manifest.packages.db.url;
  } else throw err;
}

Prevention

When it happens

Trigger: download() is called on a URL (manifest, tgz, or lockfile) whose streamed body exceeds 32 MiB — a wrong URL returning a huge file, a compromised/misbehaving CDN path, or a manifest pin pointing at the wrong blob.

Common situations: A typod or attacker-controlled URL serving arbitrary large content; the manifest's blob URL hijacked; fetching a directory listing or error page that is unexpectedly huge; fetching the wrong endpoint (e.g. an S3 bucket listing instead of a blob).

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

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

    exec("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund", "--update-notifier=false", "--cache", path.join(scratch, "empty-cache"),
      "--registry=https://registry.npmjs.org"], { cwd: scratch, stdio: "inherit", timeout: 180_000 });
    for (const name of names) assertMetadata(JSON.parse(readFileSync(path.join(scratch, "node_modules", "@paperclipai", name, "package.json"), "utf8")), `@paperclipai/${name}`, sha);
    exec(process.execPath, ["--input-type=module", "--eval", "await import('@paperclipai/db'); await import('@paperclipai/shared');"], { cwd: scratch, stdio: "inherit", timeout: 30_000 });
  } finally { rmSync(scratch, { recursive: true, force: true }); }
}

async function download(url, fetchImpl) {
  const response = await fetchImpl(url, { redirect: "error", signal: AbortSignal.timeout(60_000) });
  if (!response.ok) throw new Error(`Artifact download failed: HTTP ${response.status}`, { cause: { status: response.status } });
  const reader = response.body.getReader();
  const chunks = [];
  let size = 0;
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      size += value.length;
      if (size > maximumBytes) throw new Error("Artifact exceeds size limit.");
      chunks.push(value);
    }
  } finally { await reader.cancel(); }
  return Buffer.concat(chunks);
}

export async function verifyPublished(sha, fetchImpl = fetch, { verifyProvenance } = {}) {
  versionFor(sha);
  const bytes = await download(`${artifactBase}/${sha}/manifest.json`, fetchImpl);
  const manifest = JSON.parse(bytes);
  assertManifest(manifest, sha);
  if (verifyProvenance) await verifyProvenance(bytes, sha);
  await Promise.all(names.map(async (name) => {
    const bytes = await download(manifest.packages[name].url, fetchImpl);
    verifyBytes(bytes, manifest.packages[name]);
    assertMetadata(tarManifest(bytes), `@paperclipai/${name}`, sha);
  }));
  const lock = await download(manifest.lockfile.url, fetchImpl);

View on GitHub (pinned to 3f1d897a7c)