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
- Treat as benign when the batch result is intact — the browser.close() in the same finally reclaims the page
- Verify no renderer crash preceded it (check earlier console output for 'Target crashed')
- Update Playwright and Chromium deps if crashes repeat
- 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
- Always close pages in finally blocks when sharing one browser across a batch
- Provide complete test doubles (or none) so the typeof close() guard behaves predictably
- Restart the shared browser periodically in long batch runs
- Monitor the first error in a batch — later teardown warnings are usually its fallout
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
- ⚠️ Browser cleanup failed
- ⚠️ Context cleanup failed
- archive render skipped — no Playwright browser in env
- Blocked request to restricted destination (DNS)
- Blocked request to restricted destination (DNS)
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)