santifer/career-ops · error · Error

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

Error message

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

What it means

After loading the image into a headless Chromium page, convertImageToPdf() waits up to 10 seconds for the <img> to fully decode (img.complete with naturalWidth/naturalHeight > 0). If the wait times out, the file is presumed unreadable or corrupt and this error replaces the generic timeout.

Solutions

  1. Open the image in a viewer to confirm it is not corrupt; re-export or re-download it if broken.
  2. Verify the file size is > 0 and that the bytes match the extension (`file image.png`).
  3. Convert/re-encode the image with an image tool (`magick input output.png`) to normalize it, then retry.

Example fix

// before
await convertImageToPdf('truncated.png', 'out.pdf');
// after (guard first)
import { statSync } from 'node:fs';
if (statSync('fixed.png').size === 0) throw new Error('empty image');
await convertImageToPdf('fixed.png', 'out.pdf');
Defensive patterns

Strategy: try-catch

Validate before calling

import { statSync } from 'node:fs';
import { readFileSync } from 'node:fs';
const buf = readFileSync(p);
if (buf.length < 8 || !buf.subarray(0, 8).toString('hex').match(/^(89504e47|ffd8ff|47494638|52494646)/)) {
  throw new Error(`${p} does not look like a valid image`);
}

Try / catch

try {
  await convertImageToPdf(input, output);
} catch (err) {
  if (err.message.includes('failed to decode')) {
    console.error(`Image ${input} is corrupt or unreadable; re-export it and retry.`);
  } else throw err;
}

Prevention

When it happens

Trigger: convertImageToPdf() given a corrupt/truncated image file, a zero-byte file, a file whose bytes do not match its extension, or a path pointing at something that loads but never decodes (e.g. an HTML file named .png).

Common situations: Interrupted downloads producing truncated files; images with wrong extensions; files locked or still being written by another process during conversion.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/7dc81207805e47f5. Report an issue: GitHub.

Appendix: 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 aac998c7ed)