{"record":{"id":"dcb7c3bc50e5c870","repo":"nexu-io/open-design","slug":"task-folder-spritesheet-is-not-webp-png-gif","errorCode":null,"errorMessage":"${task.folder}: spritesheet is not webp/png/gif","messagePattern":"(.+?): spritesheet is not webp/png/gif","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/daemon/src/community-pets-sync.ts","lineNumber":215,"sourceCode":"  const dir = path.join(outRoot, task.folder);\n  const sheetPath = path.join(dir, `spritesheet.${task.spritesheetExt}`);\n  const manifestPath = path.join(dir, 'pet.json');\n  if (!force && (await pathExists(sheetPath)) && (await pathExists(manifestPath))) {\n    return 'skipped';\n  }\n  await mkdir(dir, { recursive: true });\n  const bytes = await downloadBinary(task.spritesheetUrl);\n  if (bytes.length < 16) {\n    throw new Error(`${task.folder}: spritesheet too small (${bytes.length} bytes)`);\n  }\n  // Reject HTML error pages dressed as `.webp` so the UI doesn't end up\n  // adopting a pet whose sprite is `<!doctype html>`.\n  const head = bytes.subarray(0, 12);\n  const isWebp = head.toString('ascii', 0, 4) === 'RIFF' && head.toString('ascii', 8, 12) === 'WEBP';\n  const isPng = head.toString('hex', 0, 8) === '89504e470d0a1a0a';\n  const isGif = head.toString('ascii', 0, 6) === 'GIF87a' || head.toString('ascii', 0, 6) === 'GIF89a';\n  if (!isWebp && !isPng && !isGif) {\n    throw new Error(`${task.folder}: spritesheet is not webp/png/gif`);\n  }\n  await writeFile(sheetPath, bytes);\n  await writeFile(manifestPath, JSON.stringify(task.manifest, null, 2) + '\\n', 'utf8');\n  return 'wrote';\n}\n\nasync function runPool<T, R>(\n  items: T[],\n  concurrency: number,\n  worker: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n  const results: R[] = new Array(items.length);\n  let cursor = 0;\n  const workers = Array.from(\n    { length: Math.min(concurrency, items.length) },\n    async () => {\n      for (;;) {\n        const idx = cursor++;","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/community-pets-sync.ts#L197-L233","documentation":"Thrown by writePet when the downloaded binary passes the size check (>=16 bytes) but its magic bytes don't match webp (RIFF...WEBP), png (89 50 4E 47...), or gif (GIF87a/GIF89a) signatures. This is the HTML-error-page guard: CDNs sometimes return a 200 OK with an HTML error page, and without this check the UI would adopt a pet whose sprite is <!doctype html>. The comment at line 211 documents this exact scenario.","triggerScenarios":"downloadBinary returns a body that starts with <!doctype html> or some other non-image content, but is >=16 bytes so it passes the size check. The magic-byte inspection at lines 214-216 fails all three format checks, and the error fires at line 215.","commonSituations":"The spritesheet URL returns an HTML 404/error page with HTTP 200 (common with SPA CDNs). A CDN serves a generic placeholder image in a format the sync doesn't support (e.g., JPEG, AVIF). The hosting service wraps responses in HTML.","solutions":["Open the spritesheetUrl directly to verify it serves a webp/png/gif image.","If the host serves JPEG or another format, extend the format check or convert the asset.","Check if the CDN is returning an HTML error page for a missing asset.","Mark the pet as failed if the asset is permanently in an unsupported format."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// After download, validate the magic bytes before writing.\nconst MAGIC = {\n  webp: (b: Buffer) => b.toString('ascii', 0, 4) === 'RIFF' && b.toString('ascii', 8, 12) === 'WEBP',\n  png: (b: Buffer) => b.toString('hex', 0, 8) === '89504e470d0a1a0a',\n  gif: (b: Buffer) => {\n    const sig = b.toString('ascii', 0, 6);\n    return sig === 'GIF87a' || sig === 'GIF89a';\n  },\n};\n\nfunction detectImageFormat(bytes: Buffer): 'webp' | 'png' | 'gif' | null {\n  if (bytes.length < 12) return null;\n  if (MAGIC.webp(bytes)) return 'webp';\n  if (MAGIC.png(bytes)) return 'png';\n  if (MAGIC.gif(bytes)) return 'gif';\n  return null;\n}\n\n// Use to pre-validate or to diagnose\nconst fmt = detectImageFormat(bytes);\nif (!fmt) {\n  console.warn(`Unexpected content-type for ${url}: ${bytes.subarray(0, 32).toString('utf8')}`);\n}","typeGuard":"export function isSpritesheetFormatError(error: unknown): boolean {\n  return error instanceof Error && error.message.includes('spritesheet is not webp/png/gif');\n}","tryCatchPattern":"try {\n  await writePet(task, outRoot, force);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('spritesheet is not webp/png/gif')) {\n    // The CDN likely served an HTML error page — skip this pet\n    syncResult.failed++;\n    syncResult.errors.push(error.message);\n    continue;\n  } else throw error;\n}","preventionTips":["Open the spritesheet URL in a browser to verify it serves a real image.","If the host serves JPEG or AVIF, extend the format check or pre-convert.","Treat HTML responses (CDN error pages) as broken assets, not transient failures.","Log the first few bytes of unexpected payloads to diagnose CDN behavior."],"tags":["community-pets","validation","data-integrity","file-format"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}