santifer/career-ops · error · Error

Image failed to decode within 10s (unreadable or corrupt fil

Error message

Image failed to decode within 10s (unreadable or corrupt file?): ${inputPath}

What it means

Thrown by convertImageToPdf() in img-to-pdf.mjs when the in-page <img> did not reach naturalWidth>0 && naturalHeight>0 && complete within a 10-second Playwright waitForFunction. This means Chromium could load the HTML shell but the embedded base64 image never decoded — almost always a corrupt, truncated, or zero-byte source file rather than a slow network (the image is inline base64, so there is no network).

Source

Thrown at img-to-pdf.mjs:140

<body>
<img id="career-ops-img" src="data:${mimeType};base64,${base64}">
</body>
</html>`;

  await mkdir(dirname(outputPath), { recursive: true });

  const browser = await chromium.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setContent(html, { waitUntil: 'load' });

    try {
      await page.waitForFunction(() => {
        const img = document.getElementById('career-ops-img');
        return !!img && img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;
      }, { timeout: 10000 });
    } catch (err) {
      throw new Error(`Image failed to decode within 10s (unreadable or corrupt file?): ${inputPath}`);
    }

    const { width, height } = await page.evaluate(() => {
      const img = document.getElementById('career-ops-img');
      return { width: img.naturalWidth, height: img.naturalHeight };
    });

    // 96 CSS px per inch is the standard browser/Playwright conversion —
    // sizing the PDF page to the image's own dimensions means the image
    // fills the page exactly: no cropping, no blank margins.
    const widthIn = width / 96;
    const heightIn = height / 96;

    const pdfBuffer = await page.pdf({
      width: `${widthIn}in`,
      height: `${heightIn}in`,
      margin: { top: '0', right: '0', bottom: '0', left: '0' },
      printBackground: true,

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Open the source image in a normal viewer to confirm it decodes — if it doesn't, re-download or re-export the original.
  2. Check the file is not zero-byte: `ls -l input.png` and `file input.png` to confirm type matches extension.
  3. For SVGs, validate the XML: `xmllint --noout input.svg`.
  4. Re-export from the source application (Photoshop/Figma/etc.) to a fresh PNG/JPEG.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate the file is non-empty and decodable-ish before Chromium
const stat = await fsPromises.stat(inputPath);
if (stat.size === 0) throw new Error('Image file is zero bytes: ' + inputPath);

Try / catch

try {
  return await convertImageToPdf(inputPath, outputPath);
} catch (e) {
  if (e.message.includes('failed to decode within 10s')) {
    // almost always corrupt/truncated source; re-export from original
    throw new Error(`Image appears corrupt: ${inputPath}. Re-export the original as PNG/JPEG.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: The source file is truncated (download interrupted), zero-byte, a valid extension but wrong contents (e.g. a .png that is actually HTML), or a degenerate image Chromium's decoder rejects. waitForFunction times out at 10000ms and the catch rethrows this message.

Common situations: Partially downloaded image; a file renamed from another format; an SVG with invalid XML that Chromium cannot rasterize; a corrupt BMP/GIF; disk read returned short.

Understand the failure class

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/7dc81207805e47f5. Report an issue: GitHub.