{"record":{"id":"b86842692f4e5085","repo":"santifer/career-ops","slug":"page-cleanup-failed-err-message","errorCode":null,"errorMessage":"⚠️  Page cleanup failed: ${err.message}","messagePattern":"⚠️  Page cleanup failed: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"generate-pdf.mjs","lineNumber":1584,"sourceCode":"    console.log(`📊 Pages: ${pageCount}`);\n    console.log(`📦 Size: ${(pdfBuffer.length / 1024).toFixed(1)} KB`);\n\n    try {\n      updatePDFManifest(reportNum, outputPath, inputPath, format);\n      console.log(`🔗 Manifest: data/pdf-index.tsv updated${reportNum ? ` (report ${reportNum})` : ' (no --report given)'}`);\n    } catch (err) {\n      // The PDF itself succeeded — never fail the run over manifest bookkeeping.\n      console.error(`⚠️  Manifest update failed: ${err.message}`);\n    }\n\n    return { outputPath, pageCount, size: pdfBuffer.length };\n  } finally {\n    // Close the page so a batch does not accumulate pages into the shared\n    // browser (leak → OOM). Optional-chained: the single path's browser.close()\n    // already reclaims the page, and minimal test doubles may omit close().\n    if (page && typeof page.close === 'function') {\n      await page.close().catch((err) => {\n        console.warn(`⚠️  Page cleanup failed: ${err.message}`);\n      });\n    }\n    // Close the per-document context too, so the JS-disabled context created\n    // above does not accumulate in the shared browser across a batch (#2384).\n    if (context && typeof context.close === 'function') {\n      await context.close().catch((err) => {\n        console.warn(`⚠️  Context cleanup failed: ${err.message}`);\n      });\n    }\n    // Clean up temp file\n    await unlink(tmpHtmlPath).catch((err) => {\n      if (err?.code !== 'ENOENT') {\n        console.warn(`⚠️  Temporary HTML cleanup failed: ${err.message}`);\n      }\n    });\n  }\n}\n","sourceCodeStart":1566,"sourceCodeEnd":1602,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/generate-pdf.mjs#L1566-L1602","documentation":"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().","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// For test doubles: make close() resolve so the best-effort path stays silent\nfunction makeFakePage() {\n  return { close: async () => {}, goto: async () => {}, pdf: async () => Buffer.from('') };\n}","typeGuard":"function isCloseablePage(p) {\n  return p != null && typeof p.close === 'function';\n}","tryCatchPattern":"// Pattern already applied by renderInPage's finally; mirror it if you manage pages yourself:\nif (page && typeof page.close === 'function') {\n  await page.close().catch((err) => console.warn(`page close failed (ignored): ${err.message}`));\n}","preventionTips":["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"],"tags":["playwright","cleanup","page-lifecycle","batch-render","best-effort"],"backgroundTag":"resource-cleanup-failed","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}