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
- Verify the URL matches the expected content-addressed form `${artifactBase}/blobs/<sha512hex>.<ext>` before fetching
- Re-fetch the manifest from `${artifactBase}/<sha>/manifest.json` and re-run verifyPublished so pins and URLs come from the trusted manifest
- 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
- Fetch only content-addressed blob URLs taken from an already-verified manifest
- Never pass unverified user-supplied URLs to download()
- Check content-length with a HEAD request before streaming large payloads
- Treat size-limit violations as a security signal, not just a capacity issue
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
- Artifact download failed: HTTP
- A trusted viewer build is required for public chat reports
- Access denied
- ACPX runtime executable changed while it was verified
- ACPX runtime executable could not be opened as a no-follow…
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)