paperclipai/paperclip · error
Decoded image exceeds the pixel limit
Error message
Decoded image exceeds the pixel limit
What it means
validatePhotonImage decodes metadata via sharp (with limitInputPixels: MAX_PIXELS and failOn: 'error') and throws when width/height are missing or width*height*pages exceeds MAX_PIXELS (50,000,000). This rejects decoded images that are too large — including animated/multi-page formats where total pixels scale by page count.
Solutions
- Downscale client-side before upload (e.g. browser canvas or sharp resize to max 8192px) to stay under 50MP.
- If it's an animated GIF/WebP, trim frames or convert to a video/shorter animation so width*height*pages fits the budget.
- If larger images are a legitimate product need, raise MAX_PIXELS in media.ts (and note sharp's memory implications).
- Catch this error in ingestAttachments and return a 413/422 with a user-facing 'image resolution too high' message rather than failing the whole request.
Example fix
// before
await validatePhotonImage(rawBuffer, 'image/png'); // 80MP panorama -> throws
// after
const resized = await sharp(rawBuffer).resize({ width: 8000, withoutEnlargement: true }).png().toBuffer();
await validatePhotonImage(resized, 'image/png'); Defensive patterns
Strategy: validation
Validate before calling
import sharp from 'sharp';
export async function isWithinPixelLimit(body: Buffer): Promise<boolean> {
const meta = await sharp(body, { limitInputPixels: 50_000_000 }).metadata().catch(() => null);
if (!meta?.width || !meta?.height) return false;
return meta.width * meta.height * (meta.pages ?? 1) <= 50_000_000;
} Type guard
function hasBoundedDecodedSize(m: { width?: number; height?: number; pages?: number }): boolean {
return typeof m.width === 'number' && typeof m.height === 'number' &&
m.width > 0 && m.height > 0 &&
m.width * m.height * (m.pages ?? 1) <= 50_000_000;
} Try / catch
try {
await validatePhotonImage(body, contentType);
} catch (err) {
if (err instanceof Error && err.message === 'Decoded image exceeds the pixel limit') {
const downscaled = await sharp(body).resize({ width: 8192, withoutEnlargement: true }).toBuffer();
return validatePhotonImage(downscaled, contentType);
}
throw err;
} Prevention
- Downscale large uploads client-side before sending
- Remember animated formats count pages: cap frame count for GIF/WebP
- Check dimensions in the browser via createImageBitmap before upload
- Keep sharp's limitInputPixels and the MAX_PIXELS check consistent
When it happens
Trigger: Calling validatePhotonImage (from ingestAttachments or photonHeifPreview's re-validation of converted JPEG) with a non-HEIF image/* buffer whose decoded dimensions exceed 50MP, or an animated GIF/WebP whose frame count times frame size exceeds 50MP; also zero-dimension metadata from corrupt images.
Common situations: Users uploading 60MP+ DSLR panoramas; large animated GIFs with hundreds of frames; corrupt images where sharp returns no width/height; screenshots at extreme resolutions.
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
- Image bytes do not match the declared content type
- HEIF decoded image exceeds the pixel limit
- HEIF dimensions could not be verified
- HEIF image collection exceeds the pixel limit
- A full lowercase source SHA is required.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/444ed354c5157629.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/photon/media.ts:88
throw new Error("HEIF dimensions could not be verified");
}
export async function validatePhotonImage(
body: Buffer,
contentType: string,
): Promise<void> {
if (!contentType.startsWith("image/")) return;
if (HEIF_CONTENT_TYPES.has(contentType)) return validateHeifDimensions(body);
const metadata = await sharp(body, {
limitInputPixels: MAX_PIXELS,
failOn: "error",
}).metadata();
if (
!metadata.width ||
!metadata.height ||
metadata.width * metadata.height * (metadata.pages ?? 1) > MAX_PIXELS
)
throw new Error("Decoded image exceeds the pixel limit");
const formats: Record<string, string[]> = {
"image/jpeg": ["jpeg"],
"image/jpg": ["jpeg"],
"image/png": ["png"],
"image/webp": ["webp"],
"image/gif": ["gif"],
};
if (!formats[contentType]?.includes(metadata.format ?? ""))
throw new Error("Image bytes do not match the declared content type");
}
/** Isolate the native converter with a deadline and bounded input/output.
* Native packages exist for macOS, Windows and Linux glibc x64/arm64.
* Unsupported hosts retain the original and report an unavailable preview. */
export async function photonHeifPreview(body: Buffer): Promise<Buffer> {
validateHeifDimensions(body);
const modulePath = require.resolve("heif2jpeg");
const script = `const {heifToJpeg}=require(process.argv[1]);const chunks=[];process.stdin.on('data',c=>chunks.push(c));process.stdin.on('end',async()=>{try{const jpeg=await heifToJpeg(Buffer.concat(chunks),{quality:80});process.stdout.end(jpeg);}catch{process.exitCode=1;}});`;View on GitHub (pinned to 3f1d897a7c)