santifer/career-ops · warning
⚠️ Context cleanup failed: ${err.message}
Error message
⚠️ Context cleanup failed: ${err.message} What it means
Playwright's BrowserContext.close() rejected during the finally-block cleanup of the single-document PDF render path in generate-pdf.mjs. This close exists so the per-document JS-disabled context does not accumulate in the shared browser across a batch (#2384); when it fails, the context's resources are only reclaimed later, when the shared browser itself closes. Usual causes: the Chromium process already died (crash or OOM kill), an earlier failure path already tore the context down (double close), or the Playwright driver lost its connection.
Source
Thrown at generate-pdf.mjs:1591
// 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}`);
}
});
}
}
/**
* Render many already-normalized HTML documents through ONE shared Chromium.
*
* Maintainer conditions (#2384): the browser is launched once via the same
* opts.launchBrowser seam the single path uses, and closed in a finally at the
* batch boundary — it never outlives the batch and is torn down even if a
* document throws. Each entry renders on its own page (renderInPage), and aView on GitHub (pinned to 60398d6549)
Solutions
- Treat as non-fatal: verify the PDF itself was written — this warning is cleanup-only.
- If it recurs in batches, render fewer documents per browser or raise the memory ceiling; in shared containers add launch args like --disable-dev-shm-usage.
- Run npx playwright install chromium so the driver and the browser build match.
- Check dmesg or CI logs for an OOM kill of chromium before assuming a Playwright bug.
Example fix
// before
if (context && typeof context.close === 'function') {
await context.close().catch((err) => {
console.warn(`⚠️ Context cleanup failed: ${err.message}`);
});
}
// after — skip the doomed close when the shared browser is already gone
if (context && typeof context.close === 'function' && (browser ? browser.isConnected() : true)) {
await context.close().catch((err) => {
console.warn(`⚠️ Context cleanup failed: ${err.message}`);
});
} Defensive patterns
Strategy: try-catch
Validate before calling
const canCloseContext = (browser, context) =>
Boolean(context && typeof context.close === 'function') &&
(browser ? browser.isConnected() : true);
if (canCloseContext(browser, context)) {
await context.close();
} Type guard
const isCloseableContext = (c, browser) => Boolean(c && typeof c.close === 'function') && (browser ? browser.isConnected() : true);
Try / catch
await context.close().catch((err) => {
// warn-and-continue: cleanup must never mask the render result, and a
// browser that already died cannot be re-closed — expect /closed/i messages.
console.warn(`Context cleanup failed: ${err.message}`);
}); Prevention
- Attach browser.on('disconnected', ...) and clear the context/browser refs so cleanup is skipped after a crash
- Keep the playwright package and its browser binaries in lockstep (npx playwright install after every upgrade)
- Size batches so Chromium stays under the memory ceiling — an OOM-killed browser is the top cause of close() rejections
When it happens
Trigger: Chromium crashes or is OOM-killed mid-render, so context.close() rejects with 'Target closed' / 'Browser has been closed'; a prior error path already closed the context; the playwright npm package and the installed browser build are version-mismatched; a test double's close() rejects.
Common situations: Long CV batch renders under memory pressure (CI runners, containers with a small /dev/shm); upgrading playwright without running npx playwright install; minimal mocks in tests that throw on close.
Related errors
- ⚠️ Browser cleanup failed: ${err.message}
- ⚠️ Page 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/3207bb399783749f.
Report an issue: GitHub.