{"record":{"id":"3207bb399783749f","repo":"santifer/career-ops","slug":"context-cleanup-failed-err-message","errorCode":null,"errorMessage":"⚠️  Context cleanup failed: ${err.message}","messagePattern":"⚠️  Context cleanup failed: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"generate-pdf.mjs","lineNumber":1591,"sourceCode":"      // 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\n/**\n * Render many already-normalized HTML documents through ONE shared Chromium.\n *\n * Maintainer conditions (#2384): the browser is launched once via the same\n * opts.launchBrowser seam the single path uses, and closed in a finally at the\n * batch boundary — it never outlives the batch and is torn down even if a\n * document throws. Each entry renders on its own page (renderInPage), and a","sourceCodeStart":1573,"sourceCodeEnd":1609,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/generate-pdf.mjs#L1573-L1609","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif (context && typeof context.close === 'function') {\n  await context.close().catch((err) => {\n    console.warn(`⚠️  Context cleanup failed: ${err.message}`);\n  });\n}\n// after — skip the doomed close when the shared browser is already gone\nif (context && typeof context.close === 'function' && (browser ? browser.isConnected() : true)) {\n  await context.close().catch((err) => {\n    console.warn(`⚠️  Context cleanup failed: ${err.message}`);\n  });\n}","handlingStrategy":"try-catch","validationCode":"const canCloseContext = (browser, context) =>\n  Boolean(context && typeof context.close === 'function') &&\n  (browser ? browser.isConnected() : true);\nif (canCloseContext(browser, context)) {\n  await context.close();\n}","typeGuard":"const isCloseableContext = (c, browser) =>\n  Boolean(c && typeof c.close === 'function') && (browser ? browser.isConnected() : true);","tryCatchPattern":"await context.close().catch((err) => {\n  // warn-and-continue: cleanup must never mask the render result, and a\n  // browser that already died cannot be re-closed — expect /closed/i messages.\n  console.warn(`Context cleanup failed: ${err.message}`);\n});","preventionTips":["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"],"tags":["playwright","browser-context","cleanup","resource-leak","oom"],"backgroundTag":"playwright-target-closed","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}