santifer/career-ops · warning

⚠️ Page cleanup failed

Error message

⚠️  Page cleanup failed: ${err.message}

What it means

Warning logged when closing the Playwright Page after a PDF render rejects. It lives in a finally block so page teardown failures never mask the render result; the page is closed to keep shared batch browsers from accumulating pages and leaking memory into OOM.

Solutions

  1. Treat as benign when the batch result is intact — the browser.close() in the same finally reclaims the page
  2. Verify no renderer crash preceded it (check earlier console output for 'Target crashed')
  3. Update Playwright and Chromium deps if crashes repeat
  4. In tests, provide doubles whose close() resolves or omit close() entirely (the typeof guard already skips function-less doubles)

Example fix

// before
await page.close().catch((err) => console.warn(`⚠️  Page cleanup failed: ${err.message}`));
// after
await page.close().catch((err) => {
  if (!err.message.includes('Target closed')) {
    console.warn(`⚠️  Page cleanup failed: ${err.message}`);
  }
});
Defensive patterns

Strategy: try-catch

Type guard

if (page && typeof page.close === 'function') { /* safe to close */ }

Try / catch

await page.close().catch((err) => {
  if (!/Target closed|Connection closed/i.test(err.message)) {
    console.warn(`Page cleanup failed: ${err.message}`);
  }
});

Prevention

When it happens

Trigger: page.close() rejects in the finally of renderInPage batching — page already crashed (target closed), browser connection lost before the page could be closed, or a minimal test double implementing the page without a working close().

Common situations: Long batch runs where an earlier render crashed the shared browser; page killed by navigation abort or timeout; unit tests injecting stub page objects whose close() throws.

Related errors


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

Appendix: source

Thrown at generate-pdf.mjs:1799

    console.log(`📊 Pages: ${pageCount}`);
    console.log(`📦 Size: ${(pdfBuffer.length / 1024).toFixed(1)} KB`);

    try {
      updatePDFManifest(reportNum, outputPath, inputPath, format);
      console.log(`🔗 Manifest: data/pdf-index.tsv updated${reportNum ? ` (report ${reportNum})` : ' (no --report given)'}`);
    } catch (err) {
      // The PDF itself succeeded — never fail the run over manifest bookkeeping.
      console.error(`⚠️  Manifest update failed: ${err.message}`);
    }

    return { outputPath, pageCount, size: pdfBuffer.length };
  } finally {
    // Close the page so a batch does not accumulate pages into the shared
    // browser (leak → OOM). Optional-chained: the single path's browser.close()
    // already reclaims the page, and minimal test doubles may omit close().
    if (page && typeof page.close === 'function') {
      await page.close().catch((err) => {
        console.warn(`⚠️  Page cleanup failed: ${err.message}`);
      });
    }
    // Close the per-document context too, so the JS-disabled context created
    // above does not accumulate in the shared browser across a batch (#2384).
    if (context && typeof context.close === 'function') {
      await context.close().catch((err) => {
        console.warn(`⚠️  Context cleanup failed: ${err.message}`);
      });
    }
    // Clean up temp file
    await unlink(tmpHtmlPath).catch((err) => {
      if (err?.code !== 'ENOENT') {
        console.warn(`⚠️  Temporary HTML cleanup failed: ${err.message}`);
      }
    });
  }
}

View on GitHub (pinned to aac998c7ed)