{"record":{"id":"c854f2eca5bdd7c2","repo":"toeverything/AFFiNE","slug":"image-format-not-supported-c854f2","errorCode":"image_format_not_supported","errorMessage":"Image format not supported: ${format}","messagePattern":"Image format not supported: (.+?)","errorType":"exception","errorClass":"ImageFormatNotSupported","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/conversation/inbox.ts","lineNumber":85,"sourceCode":"\n    for (const blob of blobs) {\n      const uploaded = await this.storage.handleUpload(blob);\n      const detectedMime =\n        sniffMime(uploaded.buffer, blob.mimetype)?.toLowerCase() ||\n        blob.mimetype;\n      let attachmentBuffer = uploaded.buffer;\n      let attachmentMimeType = detectedMime;\n\n      if (detectedMime.startsWith('image/')) {\n        try {\n          attachmentBuffer = await processImage(\n            uploaded.buffer,\n            COPILOT_IMAGE_MAX_EDGE,\n            true\n          );\n          attachmentMimeType = 'image/webp';\n        } catch {\n          throw new ImageFormatNotSupported({ format: detectedMime });\n        }\n      }\n\n      const filename = createHash('sha256')\n        .update(attachmentBuffer)\n        .digest('base64url');\n      const terminalMode = await this.access.sessionResource(\n        {\n          userId,\n          workspaceId: session.config.workspaceId,\n          docId: session.config.docId,\n          action: session.config.docId ? 'Doc.Update' : 'Workspace.Copilot',\n        },\n        [options.sessionId]\n      );\n      if (terminalMode !== 'canonical') {\n        throw new BadRequestException(\n          \"Local workspaces don't support attachments or references.\"","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/plugins/copilot/conversation/inbox.ts#L67-L103","documentation":"Thrown while processing an uploaded image attachment: the buffer's detected MIME type starts with 'image/', but processImage (resize to COPILOT_IMAGE_MAX_EDGE, convert to webp) rejected or crashed on the bytes. It wraps the failure as ImageFormatNotSupported with error code 'image_format_not_supported', naming the detected MIME in {format}. Typical causes are corrupted payloads, CMYJ/EXIF-heavy JPEGs, or exotic formats that the decoder cannot handle even though sniffing labeled them image/*.","triggerScenarios":"Uploading a truncated or corrupted image file; an image whose extension/sniffed MIME says image/* but whose codec is unsupported (e.g. HEIC mislabeled, TIFF with odd compression, 16-bit PNG edge cases); a zero-byte or text file renamed to .png so the sniffer misclassifies it.","commonSituations":"Paste/drag from clipboard produces partial buffer; client-side compression library emits nonstandard webp/jpeg; users rename files to bypass type checks.","solutions":["Re-encode the image client-side to standard JPEG/PNG/WebP before upload","Verify the file opens in an image viewer and is not truncated (check byte size > 0 and matches source)","Retry the upload — transient read/truncation during transfer can corrupt the buffer","If maintaining this code: narrow the catch to decoder-specific errors and let unexpected failures surface separately"],"exampleFix":"// before\nawait inbox.createMessage(userId, { sessionId, blob: corruptedBuffer });\n\n// after\ntry {\n  const webp = await processImage(buffer, maxEdge, true);\n  await inbox.createMessage(userId, { sessionId, blob: webp, blobMimeType: 'image/webp' });\n} catch {\n  showUserError('This image format is not supported. Please use PNG, JPEG or WebP.');\n}","handlingStrategy":"validation","validationCode":"const sniffed = await imageType(buffer); // e.g. file-type\nconst SUPPORTED = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];\nif (!sniffed || !SUPPORTED.includes(sniffed.mime)) {\n  return rejectUpload('Use PNG, JPEG, WebP or GIF');\n}\nconst probe = await loadImage(buffer); // throws on truncated/unsupported bytes\nif (buffer.length === 0) return rejectUpload('Empty file');","typeGuard":"const isDecodableImage = async (buf: Buffer): Promise<boolean> => {\n  try { await sharp(buf).metadata(); return true; } catch { return false; }\n};","tryCatchPattern":"try {\n  await inbox.createMessage(userId, { sessionId, blob });\n} catch (e) {\n  if (e.code === 'image_format_not_supported') {\n    notifyUser(`Unsupported image format: ${e.format ?? 'unknown'}. Re-encode as PNG/JPEG/WebP.`);\n  } else throw e;\n}","preventionTips":["Client-side pre-encode uploads to a standard format and verify decodability before sending","Check file size > 0 and matches the source after any transfer/compression step","Surface the reported {format} to the user so they know which file failed"],"tags":["copilot","image-processing","upload","validation"],"backgroundTag":"unsupported-media-type","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}