santifer/career-ops · warning

⚠️ Context cleanup failed

Error message

⚠️  Context cleanup failed: ${err.message}

What it means

Warning emitted when closing the per-document BrowserContext rejects. Each rendered document gets its own (JS-disabled) context so contexts do not accumulate in the shared browser across a batch (#2384); a failed close means the context may linger in memory until the browser itself closes.

Solutions

  1. Benign if the browser is closed right after in the same finally block — contexts die with it
  2. Reduce batch size / add periodic browser restarts to avoid mid-batch crashes
  3. Run npx playwright install to align driver and browser versions
  4. If contexts accumulate without warnings being the cause, check for code paths that skip the finally
Defensive patterns

Strategy: try-catch

Type guard

if (context && typeof context.close === 'function') { /* safe to close */ }

Try / catch

await context.close().catch((err) => {
  if (!/Target closed|Connection closed/i.test(err.message)) {
    console.warn(`Context cleanup failed: ${err.message}`);
  }
});

Prevention

When it happens

Trigger: context.close() rejects in the finally after render — the context was already detached because the browser died, the page inside it crashed taking the context down, or the connection to the browser process was severed mid-batch.

Common situations: Shared-browser batch rendering where an earlier document crashed Chromium; context closed concurrently elsewhere; Playwright version mismatch between driver and browser build.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/3207bb399783749f. Report an issue: GitHub.

Appendix: source

Thrown at generate-pdf.mjs:1806

      // 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 a

View on GitHub (pinned to aac998c7ed)