santifer/career-ops · warning
⚠️ Temporary HTML cleanup failed
Error message
⚠️ Temporary HTML cleanup failed: ${err.message} What it means
Warning logged when deleting the temporary HTML file written for the render fails with any error other than ENOENT. The temp file (tmpHtmlPath) is page.setContent/page.goto input during rendering; a stale temp file is left behind on disk if this fires.
Solutions
- Ignore if rare — one stale temp file is harmless; delete it manually
- Check permissions/ownership of the temp directory and run with write access
- Ensure parallel runs do not share the same temp path
- Free disk space if ENOSPC
Defensive patterns
Strategy: try-catch
Validate before calling
import { accessSync, constants, statSync } from 'node:fs';
function canUnlink(p) {
try { statSync(p); return constants.W_OK; } catch { return false; }
} Try / catch
await unlink(tmpHtmlPath).catch((err) => {
if (err?.code !== 'ENOENT') console.warn(`Temp cleanup failed: ${err.message}`);
}); Prevention
- Write temp files to os.tmpdir() with unique names (fs.mkdtemp) per run
- Never share a temp path between concurrent runs
- Ensure the temp directory is writable and has free space
- Treat ENOENT on cleanup as success, as the code already does
When it happens
Trigger: fs unlink(tmpHtmlPath) rejects with a non-ENOENT code in the finally block — filesystem permission issue, temp file on a read-only or full volume, or the file locked/removed by another process (EBUSY/EPERM) while the renderer still held it open.
Common situations: Running with a read-only output/tmp directory (container with restricted mounts); disk quota exceeded; two generate-pdf runs in parallel using the same temp path; Windows file-locking in WSL setups.
Related errors
- Could not release report reservation
- ⚠️ Could not release report reservation
- apify: JD cache write failed for
- ⚠️ Browser cleanup failed
- cannot read
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/7f7c51c1e6663bc0.
Report an issue: GitHub.
Appendix: source
Thrown at generate-pdf.mjs:1812
// 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
* failing entry is captured as `{ok:false, error}` without stopping the rest.
*
* The browser is owned here: renderBatch closes whatever launchBrowser returns.
* launchBrowser is a launch *factory*, not a caller-owned handle, so this does
* not break test injection — the stub returns a fresh browser to be closed.
*View on GitHub (pinned to aac998c7ed)