{"record":{"id":"4e1c39013b0cee73","repo":"nexu-io/open-design","slug":"image-has-unsupported-extension-ext-use-pn","errorCode":null,"errorMessage":"--image has unsupported extension \"${ext}\". Use png, jpg, jpeg, webp, or gif.","messagePattern":"--image has unsupported extension \"(.+?)\"\\. Use png, jpg, jpeg, webp, or gif\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":261,"sourceCode":"  const MAX_IMAGE_BYTES = 16 * 1024 * 1024;\n  if (info.size > MAX_IMAGE_BYTES) {\n    throw new Error(\n      `--image too large (${info.size} bytes; max ${MAX_IMAGE_BYTES}).`,\n    );\n  }\n  const bytes = await readFile(abs);\n  const ext = path.extname(abs).toLowerCase();\n  // Tight allowlist: only what i2v / image-edit endpoints actually\n  // consume. Avoids smuggling arbitrary content through as data URLs.\n  const mime = ({\n    '.png': 'image/png',\n    '.jpg': 'image/jpeg',\n    '.jpeg': 'image/jpeg',\n    '.webp': 'image/webp',\n    '.gif': 'image/gif',\n  })[ext];\n  if (!mime) {\n    throw new Error(\n      `--image has unsupported extension \"${ext}\". Use png, jpg, jpeg, webp, or gif.`,\n    );\n  }\n  return {\n    path: rel.trim(),\n    abs,\n    mime,\n    size: bytes.length,\n    dataUrl: `data:${mime};base64,${bytes.toString('base64')}`,\n  };\n}\n\nfunction clampNumber(value: unknown, allowed: number[]): number | undefined {\n  // Accept exact registry values; otherwise snap to the nearest allowed\n  // bucket so a hallucinated `Number.MAX_SAFE_INTEGER` can't bill an\n  // entire month of credits when real providers plug in.\n  if (typeof value !== 'number' || !Number.isFinite(value)) return undefined;\n  if (allowed.length === 0) return undefined;","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L243-L279","documentation":"Thrown by resolveProjectImage after reading the file: the extension is not in the tight allowlist of png, jpg, jpeg, webp, gif. The allowlist matches exactly what i2v / image-edit upstream endpoints consume, preventing arbitrary content from being smuggled through as a data URL.","triggerScenarios":"Passing `--image assets/icon.svg`, `.bmp`, `.tiff`, `.heic`, `.avif`, or an extensionless/renamed file; a file whose extension casing differs (handled by lowercasing, so only genuinely unsupported extensions fail).","commonSituations":"SVG icons (not bitmap-supported); modern formats (AVIF/HEIC) not yet allowlisted; extensionless exports; renamed files where the extension no longer reflects content.","solutions":["Convert the image to png, jpg/jpeg, webp, or gif before referencing it.","Rename the file to a correct, supported extension that matches its actual content.","For SVGs, rasterize to PNG at the target size first.","For HEIC/AVIF, transcode with an image library to JPEG/WebP."],"exampleFix":"// before\n--image assets/logo.svg\n// after\nsharp('assets/logo.svg').png().toFile('assets/logo.png')\n--image assets/logo.png","handlingStrategy":"validation","validationCode":"const ALLOWED = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']);\nif (!ALLOWED.has(path.extname(abs).toLowerCase())) throw new Error('unsupported image extension');","typeGuard":"function isAllowedImageExt(abs: string): boolean {\n  return new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']).has(path.extname(abs).toLowerCase());\n}","tryCatchPattern":null,"preventionTips":["Convert SVG/HEIC/AVIF/BMP to PNG/JPEG/WebP/GIF first.","Ensure file extensions match actual content.","Validate extension in the UI before submit."],"tags":["media","validation","unsupported-format"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}