paperclipai/paperclip · error · Error

Invalid or oversized

Error message

Invalid or oversized ${kind}

What it means

For each declared image/animation asset, the publish script stats the file and enforces a size cap: ANNOUNCEMENT_ANIMATION_MAX_BYTES for animations and ANNOUNCEMENT_IMAGE_MAX_BYTES for images. This error means the asset path does not exist, is not a regular file (e.g. a directory or symlink), or exceeds its kind's maximum size.

Solutions

  1. Optimize or compress the asset until it fits under the per-kind byte maximum (e.g. resize the PNG, re-encode the WebP/animation)
  2. Confirm the file exists at the exact path in manifest.announcement[kind].path relative to the source directory
  3. Ensure the path is a regular file, not a directory or symlink
  4. Check the configured maximum constants if the budget legitimately changed and update the asset accordingly

Example fix

// before
assetStat.size = 6_400_000 // exceeds image max
// after
pnpm exec sharp-cli resize 1200 -o assets assets/<digest>.png
Defensive patterns

Strategy: validation

Validate before calling

const { statSync } = require("node:fs");
for (const kind of ["image", "animation"]) {
  const a = manifest.announcement?.[kind];
  if (a && statSync(path.join(source, a.path)).size > (kind === "animation" ? ANIM_MAX : IMG_MAX)) throw new Error(`${kind} too large`);
}

Type guard

const isRegularFile = async (p: string) => { try { return (await lstat(p)).isFile(); } catch { return false; } };

Try / catch

try { await prepareAnnouncementPublish(src, staging, prefix); } catch (e) { if (e.message.startsWith("Invalid or oversized")) { /* compress or fix asset path */ } }

Prevention

When it happens

Trigger: manifest.announcement[kind].path points to a nonexistent file under <source>/; the resolved path is a directory or symlink; the PNG/WebP/JPEG or HTML animation is larger than the configured byte maximum.

Common situations: Committing an unoptimized screenshot or GIF that exceeds the asset budget; renaming assets without updating current.json; forgetting to copy the asset into the bundle; lstat hitting a leftover directory where the file should be.

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/b50892e5ed06dbf0. Report an issue: GitHub.

Appendix: source

Thrown at scripts/publish-announcements.ts:58

export async function prepareAnnouncementPublish(sourceDirectory: string, staging?: string, hostPrefix?: string) {
  const prefix = announcementPublishPrefix(staging, hostPrefix);
  const source = path.resolve(sourceDirectory);
  if (!(await lstat(source)).isDirectory()) throw new Error("Source must be a real directory");
  const manifestPath = path.join(source, "current.json");
  const stat = await lstat(manifestPath);
  if (!stat.isFile() || stat.size > ANNOUNCEMENT_MANIFEST_MAX_BYTES) throw new Error("Invalid or oversized current.json");
  const manifest = announcementManifestSchema.parse(JSON.parse(await readFile(manifestPath, "utf8")));
  const files: Array<{ file: string; key: string; contentType: string; cacheControl: string }> = [];
  for (const kind of ["image", "animation"] as const) {
    const asset = manifest.announcement?.[kind];
    if (!asset) continue;
    if (!(await lstat(path.join(source, "assets"))).isDirectory()) throw new Error("Assets must be a real directory");
    const assetPath = asset.path;
    const file = path.join(source, assetPath);
    const assetStat = await lstat(file);
    const maximum = kind === "animation" ? ANNOUNCEMENT_ANIMATION_MAX_BYTES : ANNOUNCEMENT_IMAGE_MAX_BYTES;
    if (!assetStat.isFile() || assetStat.size > maximum) throw new Error(`Invalid or oversized ${kind}`);
    const bytes = await readFile(file);
    const digest = createHash("sha256").update(bytes).digest("hex");
    if (!assetPath.startsWith(`assets/${digest}.`)) throw new Error("Asset filename must match its SHA-256 digest");
    if (kind === "animation") validateAnnouncementAnimation(bytes);
    files.push({ file, key: `${prefix}/${assetPath}`, contentType: kind === "animation" ? "text/html" : assetPath.endsWith(".png") ? "image/png" : assetPath.endsWith(".jpg") ? "image/jpeg" : "image/webp", cacheControl: "public,max-age=31536000,immutable" });
  }
  files.push({ file: manifestPath, key: `${prefix}/current.json`, contentType: "application/json", cacheControl: "public,max-age=300" });
  return { manifest, files };
}

export function announcementUploadArgs(bucket: string, file: Awaited<ReturnType<typeof prepareAnnouncementPublish>>["files"][number]) {
  return ["s3api", "put-object", "--bucket", bucket, "--key", file.key, "--body", file.file,
    "--content-type", file.contentType, "--cache-control", file.cacheControl];
}

async function main() {
  const { sourceDirectory, staging, publish } = parseAnnouncementPublishArgs(process.argv.slice(2));
  const hostPrefix = process.env.PAPERCLIP_PAGE_DEFAULT_PREFIX;

View on GitHub (pinned to 3f1d897a7c)