{"record":{"id":"b50892e5ed06dbf0","repo":"paperclipai/paperclip","slug":"invalid-or-oversized-kind","errorCode":null,"errorMessage":"Invalid or oversized ${kind}","messagePattern":"Invalid or oversized (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/publish-announcements.ts","lineNumber":58,"sourceCode":"\nexport async function prepareAnnouncementPublish(sourceDirectory: string, staging?: string, hostPrefix?: string) {\n  const prefix = announcementPublishPrefix(staging, hostPrefix);\n  const source = path.resolve(sourceDirectory);\n  if (!(await lstat(source)).isDirectory()) throw new Error(\"Source must be a real directory\");\n  const manifestPath = path.join(source, \"current.json\");\n  const stat = await lstat(manifestPath);\n  if (!stat.isFile() || stat.size > ANNOUNCEMENT_MANIFEST_MAX_BYTES) throw new Error(\"Invalid or oversized current.json\");\n  const manifest = announcementManifestSchema.parse(JSON.parse(await readFile(manifestPath, \"utf8\")));\n  const files: Array<{ file: string; key: string; contentType: string; cacheControl: string }> = [];\n  for (const kind of [\"image\", \"animation\"] as const) {\n    const asset = manifest.announcement?.[kind];\n    if (!asset) continue;\n    if (!(await lstat(path.join(source, \"assets\"))).isDirectory()) throw new Error(\"Assets must be a real directory\");\n    const assetPath = asset.path;\n    const file = path.join(source, assetPath);\n    const assetStat = await lstat(file);\n    const maximum = kind === \"animation\" ? ANNOUNCEMENT_ANIMATION_MAX_BYTES : ANNOUNCEMENT_IMAGE_MAX_BYTES;\n    if (!assetStat.isFile() || assetStat.size > maximum) throw new Error(`Invalid or oversized ${kind}`);\n    const bytes = await readFile(file);\n    const digest = createHash(\"sha256\").update(bytes).digest(\"hex\");\n    if (!assetPath.startsWith(`assets/${digest}.`)) throw new Error(\"Asset filename must match its SHA-256 digest\");\n    if (kind === \"animation\") validateAnnouncementAnimation(bytes);\n    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\" });\n  }\n  files.push({ file: manifestPath, key: `${prefix}/current.json`, contentType: \"application/json\", cacheControl: \"public,max-age=300\" });\n  return { manifest, files };\n}\n\nexport function announcementUploadArgs(bucket: string, file: Awaited<ReturnType<typeof prepareAnnouncementPublish>>[\"files\"][number]) {\n  return [\"s3api\", \"put-object\", \"--bucket\", bucket, \"--key\", file.key, \"--body\", file.file,\n    \"--content-type\", file.contentType, \"--cache-control\", file.cacheControl];\n}\n\nasync function main() {\n  const { sourceDirectory, staging, publish } = parseAnnouncementPublishArgs(process.argv.slice(2));\n  const hostPrefix = process.env.PAPERCLIP_PAGE_DEFAULT_PREFIX;","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/publish-announcements.ts#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nassetStat.size = 6_400_000 // exceeds image max\n// after\npnpm exec sharp-cli resize 1200 -o assets assets/<digest>.png","handlingStrategy":"validation","validationCode":"const { statSync } = require(\"node:fs\");\nfor (const kind of [\"image\", \"animation\"]) {\n  const a = manifest.announcement?.[kind];\n  if (a && statSync(path.join(source, a.path)).size > (kind === \"animation\" ? ANIM_MAX : IMG_MAX)) throw new Error(`${kind} too large`);\n}","typeGuard":"const isRegularFile = async (p: string) => { try { return (await lstat(p)).isFile(); } catch { return false; } };","tryCatchPattern":"try { await prepareAnnouncementPublish(src, staging, prefix); } catch (e) { if (e.message.startsWith(\"Invalid or oversized\")) { /* compress or fix asset path */ } }","preventionTips":["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"],"tags":["filesystem","file-size","announcements"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}