slopus/happy · error
Unsupported image format
Error message
Unsupported image format
What it means
processImage uses sharp to read an image's metadata and only accepts PNG and JPEG formats. Any other format (webp, gif, heic, avif, svg, etc.) makes it throw 'Unsupported image format'.
Source
Thrown at packages/happy-server/sources/storage/processImage.ts:11
import { thumbhash } from "./thumbhash";
export async function processImage(src: Buffer) {
const sharp = (await import("sharp")).default;
// Read image
let meta = await sharp(src).metadata();
let width = meta.width!;
let height = meta.height!;
if (meta.format !== 'png' && meta.format !== 'jpeg') {
throw new Error('Unsupported image format');
}
// Resize
let targetWidth = 100;
let targetHeight = 100;
if (width > height) {
targetHeight = Math.round(height * targetWidth / width);
} else if (height > width) {
targetWidth = Math.round(width * targetHeight / height);
}
// Resize image
const { data, info } = await sharp(src).resize(targetWidth, targetHeight).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
// Thumbhash
const binaryThumbHash = thumbhash(info.width, info.height, data);
const thumbhashStr = Buffer.from(binaryThumbHash).toString('base64');
View on GitHub (pinned to b824cd0a46)
Solutions
- Convert the input image to JPEG or PNG before uploading/processing (e.g. sips -s format jpeg on macOS, ImageMagick convert)
- Extend processImage to allow more formats or to convert via sharp(src).toFormat('jpeg') instead of throwing
- Sanitize uploads server-side: reject or transcode non-png/jpeg formats early with a clear user-facing message
Example fix
// before
if (meta.format !== 'png' && meta.format !== 'jpeg') {
throw new Error('Unsupported image format');
}
// after
if (meta.format !== 'png' && meta.format !== 'jpeg') {
buf = await sharp(src).toFormat('jpeg').toBuffer();
meta = await sharp(buf).metadata();
} Defensive patterns
Strategy: validation
Validate before calling
const meta = await sharp(src).metadata();
if (meta.format !== 'png' && meta.format !== 'jpeg') {
throw new Error(`Unsupported image format: ${meta.format}; convert to png/jpeg first`);
} Type guard
function isSupportedImage(m: sharp.Metadata): boolean {
return m.format === 'png' || m.format === 'jpeg';
} Try / catch
try {
const out = await processImage(src, dest);
} catch (e) {
if (e.message === 'Unsupported image format') {
await sharp(src).toFormat('jpeg').toFile(tempJpeg);
await processImage(tempJpeg, dest);
} else throw e;
} Prevention
- Transcode uploads to JPEG/PNG at ingest time
- Validate magic bytes, not just file extensions, on upload
- Whitelist content types in your upload endpoint
- Test the pipeline with HEIC/WebP/AVIF samples
When it happens
Trigger: Calling processImage on a source image whose sharp-detected meta.format is neither 'png' nor 'jpeg' — e.g. a WebP screenshot, HEIC iPhone photo, or animated GIF passed to the thumbnailing pipeline.
Common situations: Users upload photos from mobile devices (HEIC), modern web exports (WebP/AVIF), or files with wrong extensions that don't match actual content.
Related errors
- Usage: happy acp <agent-name> or happy acp -- <command> [arg
- Missing command after "--". Usage: happy acp -- <command> [a
- Unsupported Claude goal action
- No active Claude goal
- Unsupported Codex goal action
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/2a96fe6375f777f9.
Report an issue: GitHub.