santifer/career-ops · warning
⚠️ Page cleanup failed: ${err.message}
Error message
⚠️ Page cleanup failed: ${err.message} What it means
Best-effort cleanup warning in generate-pdf.mjs's per-document finally: after the PDF result is returned (or an error propagates), page.close() rejected and is logged but never rethrown, so cleanup noise cannot overwrite a real result. The guard is defensive because the single-CV path's browser.close() already reclaims the page and minimal test doubles may omit close().
Source
Thrown at generate-pdf.mjs:1584
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 60398d6549)
Solutions
- Ignore it if the render returned — the returned {outputPath, pageCount, size} is authoritative
- For batch instability, lower batch size / disable JS injection if pages keep crashing, and watch memory
- In tests, make page/context doubles' close() resolve instead of reject
- Keep one owner per lifecycle layer: pages/contexts close in renderInPage's finally; the browser closes in renderHtmlToPdf's finally
Defensive patterns
Strategy: fallback
Validate before calling
// For test doubles: make close() resolve so the best-effort path stays silent
function makeFakePage() {
return { close: async () => {}, goto: async () => {}, pdf: async () => Buffer.from('') };
} Type guard
function isCloseablePage(p) {
return p != null && typeof p.close === 'function';
} Try / catch
// Pattern already applied by renderInPage's finally; mirror it if you manage pages yourself:
if (page && typeof page.close === 'function') {
await page.close().catch((err) => console.warn(`page close failed (ignored): ${err.message}`));
} Prevention
- Keep exactly one closer per layer: pages/contexts in renderInPage, browser in renderHtmlToPdf — never both close the same resource
- Ignore the warning when the render returned a result; investigate only if renders themselves start failing
- In long batches, monitor Chromium memory; crashed pages are the usual source of rejected closes
When it happens
Trigger: Batch rendering (renderInPage on a shared browser) where the page or its Chromium process already died (tab crash, OOM, outer browser.close() racing the per-page close), or a mocked page whose close() rejects. Triggered only when page exists and exposes a close function.
Common situations: Long batch runs accumulating crashed pages before the context/browser close; test doubles returning a page stub whose close() throws; interleaved browser-level and page-level cleanup in custom harnesses.
Related errors
- ⚠️ Browser cleanup failed: ${err.message}
- ⚠️ Context cleanup failed: ${err.message}
- refusing to archive restricted destination: ${preGuard.reaso
- refusing to archive restricted destination after redirect: $
- Invalid or blocked URL after redirect: ${finalRejected.reaso
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/b86842692f4e5085.
Report an issue: GitHub.