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
- Optimize or compress the asset until it fits under the per-kind byte maximum (e.g. resize the PNG, re-encode the WebP/animation)
- Confirm the file exists at the exact path in manifest.announcement[kind].path relative to the source directory
- Ensure the path is a regular file, not a directory or symlink
- 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
- Compress images/animations to the size budget before adding them to a bundle
- Regenerate the manifest after every asset change
- Check file existence and size locally before invoking the publish script
- Keep asset paths as regular files, never directories or symlinks
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
- Assets must be a real directory
- Invalid or oversized announcement animation
- Invalid or oversized current.json
- A trusted viewer build is required for public chat reports
- ACPX runtime executable changed while it was verified
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)