santifer/career-ops · warning
⚠️ Browser cleanup failed: ${err.message}
Error message
⚠️ Browser cleanup failed: ${err.message} What it means
Best-effort cleanup warning in generate-pdf.mjs's renderHtmlToPdf(): after renderInPage() finishes (or throws), browser.close() rejected and the rejection is only logged, never rethrown — a failed close must not mask a successful PDF render. Typical when Chromium already died (crash/OOM) or was closed twice.
Source
Thrown at generate-pdf.mjs:1448
* baseDir?: string,
* reportNum?: string,
* inputPath?: string,
* maxPages?: number,
* strictPages?: boolean,
* launchBrowser?: (options: {headless: boolean}) => Promise<import('playwright').Browser>
* }} [opts]
* @returns {Promise<{outputPath: string, pageCount: number, size: number}>}
*/
export async function renderHtmlToPdf(html, outputPath, opts = {}) {
const launchBrowser = opts.launchBrowser || ((options) => chromium.launch(options));
let browser = null;
try {
browser = await launchBrowser({ headless: true });
return await renderInPage(browser, html, outputPath, opts);
} finally {
if (browser) {
await browser.close().catch((err) => {
console.warn(`⚠️ Browser cleanup failed: ${err.message}`);
});
}
}
}
/**
* Render one already-normalized HTML document to a PDF on an already-launched
* browser. This is the page-level half of the render — it owns the per-document
* work (theme/print/font injection, temp file, page, PDF, page-budget, manifest)
* but NOT the browser lifecycle. Both the single-CV path (renderHtmlToPdf) and
* the batch path (renderBatch) call this exact function, which is what keeps a
* single-CV render byte-identical whether it runs alone or inside a batch (#2384).
*
* The page and the temp HTML file are always cleaned up in a finally, so a
* throw here (e.g. a strict page-budget overflow) never leaks a page into the
* shared browser — the caller's remaining documents keep their own fresh pages.
*
* @param {import('playwright').Browser} browser - An open browser to render on.View on GitHub (pinned to 60398d6549)
Solutions
- Treat the warning as noise if the PDF was written — check the returned outputPath/pageCount
- For repeated occurrences in batches, reduce parallelism or raise memory so Chromium stops dying
- If injecting a custom launchBrowser, do not close the browser yourself; let this function own the lifecycle
- Confirm no double-close: renderHtmlToPdf owns close(); renderInPage callers must only close pages/contexts they created
Defensive patterns
Strategy: fallback
Validate before calling
// Before a batch, sanity-check that Chromium can launch at all
const browser = await (opts.launchBrowser || ((o) => chromium.launch(o)))({ headless: true });
await browser.close().catch(() => {}); // probe succeeded if we got here Try / catch
// The function already swallows close() rejections; trust the return value:
try {
const { outputPath, pageCount } = await renderHtmlToPdf(html, out, opts);
// success — a cleanup warning afterwards is noise
} catch (err) {
// real render failure; browser cleanup already handled best-effort
throw err;
} Prevention
- Trust {outputPath, pageCount} over cleanup warnings — a failed close never marks a render failed
- Let renderHtmlToPdf own browser.close(); never close the browser you passed via launchBrowser yourself
- Cap batch parallelism and memory so Chromium stops getting OOM-killed mid-run
When it happens
Trigger: Calling renderHtmlToPdf() where the launchBrowser() chromium instance crashed mid-render (OOM-killed headless process), or a test double/outer harness already closed the browser before the finally ran. The render result is unaffected unless the crash itself failed the render earlier.
Common situations: Memory pressure in batch/parallel CV rendering killing Chromium; custom launchBrowser injection in tests that closes eagerly; CI sandboxes with low memory limits.
Related errors
- ⚠️ Page 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/208d8ea19efd2630.
Report an issue: GitHub.