{"record":{"id":"34bd01f3a554f486","repo":"santifer/career-ops","slug":"could-not-determine-the-rendered-pdf-page-count-fr","errorCode":null,"errorMessage":"Could not determine the rendered PDF page count from its page tree.","messagePattern":"Could not determine the rendered PDF page count from its page tree\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"generate-pdf.mjs","lineNumber":1070,"sourceCode":"  const objects = new Map();\n  const objectPattern = /(?:^|[\\r\\n])(\\d+)\\s+(\\d+)\\s+obj\\b([\\s\\S]*?)\\bendobj\\b/g;\n\n  for (const match of pdf.matchAll(objectPattern)) {\n    const streamIndex = match[3].search(/\\bstream(?:\\r?\\n|\\r)/);\n    const dictionary = streamIndex === -1 ? match[3] : match[3].slice(0, streamIndex);\n    objects.set(`${match[1]} ${match[2]}`, dictionary);\n  }\n\n  const catalog = [...objects.values()].find((body) => /\\/Type\\s*\\/Catalog\\b/.test(body));\n  const pagesRef = catalog?.match(/\\/Pages\\s+(\\d+)\\s+(\\d+)\\s+R\\b/);\n  const pages = pagesRef ? objects.get(`${pagesRef[1]} ${pagesRef[2]}`) : null;\n  const count = pages && /\\/Type\\s*\\/Pages\\b/.test(pages)\n    ? pages.match(/\\/Count\\s+(\\d+)\\b/)\n    : null;\n  const pageCount = count ? Number(count[1]) : 0;\n\n  if (!Number.isInteger(pageCount) || pageCount < 1) {\n    throw new Error('Could not determine the rendered PDF page count from its page tree.');\n  }\n  return pageCount;\n}\n\n/**\n * Convert a path to a workspace-relative manifest entry, or blank if it is\n * unknown or outside the tracker-owned workspace.\n *\n * @param {string} pathValue - Absolute or cwd-relative filesystem path.\n * @param {string} [rootDir] - Workspace root used as the manifest base.\n * @returns {string} Workspace-relative path using forward slashes, or an empty string.\n */\nexport function workspaceRelativeManifestPath(pathValue, rootDir = currentWorkspaceRoot()) {\n  if (!pathValue) return '';\n  const rel = relative(rootDir, resolve(pathValue));\n  if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return '';\n  return rel.split(sep).join('/');\n}","sourceCodeStart":1052,"sourceCodeEnd":1088,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/generate-pdf.mjs#L1052-L1088","documentation":"countRenderedPdfPages parses the PDF bytes Chromium produced, finds the /Type /Catalog object, follows its /Pages reference, and reads /Count from the page-tree root to get the page count. If any step fails — no catalog, no /Pages ref, no /Type /Pages object, or a missing/unparseable /Count — pageCount is 0 and this error is thrown rather than returning a bogus count.","triggerScenarios":"The PDF buffer is empty or truncated (Chromium crashed or page.goto failed), the buffer is not actually a PDF (an HTML error page was saved), or the PDF uses an incremental/compressed object layout (object streams) the regex-based parser cannot read, so the catalog or page tree is not found.","commonSituations":"Outdated or broken Chromium/Playwright returning partial PDFs; a proxy or error page captured instead of a PDF; very large PDFs with cross-reference streams; version changes in the headless browser's PDF writer.","solutions":["Verify the PDF buffer is a real, complete PDF (starts with %PDF-, non-trivial size); if not, fix the render step (Playwright page.pdf) first","Update Playwright/Chromium to a current version — old or mismatched browsers can emit PDF layouts the parser misses","Re-render the PDF; a transient Chromium failure often resolves on retry","If parsing custom PDFs, pre-flatten object streams (e.g. via qpdf) before counting pages","Inspect the failing PDF manually (pdfinfo) to confirm it has a readable page tree"],"exampleFix":"// before\nconst pdf = await page.pdf({ format: 'A4' });\nenforcePageBudget(countPages(pdf));\n// after: guard against empty/failed renders\nconst pdf = await page.pdf({ format: 'A4' });\nif (!pdf || pdf.length < 100 || !pdf.subarray(0, 5).toString('latin1').startsWith('%PDF-')) {\n  throw new Error('Chromium produced an empty or invalid PDF; check the render step');\n}\nenforcePageBudget(countRenderedPdfPages(pdf));","handlingStrategy":"try-catch","validationCode":"function isPlausiblePdf(buf) {\n  return Buffer.isBuffer(buf) && buf.length > 100 && buf.subarray(0, 5).toString('latin1') === '%PDF-';\n}\nif (!isPlausiblePdf(pdfBuffer)) throw new Error('render produced no valid PDF');","typeGuard":"const hasPageTree = (buf) => /\\/Type\\s*\\/Catalog\\b[\\s\\S]*?\\/Pages\\s+\\d+\\s+\\d+\\s+R\\b/.test(buf.toString('latin1'));","tryCatchPattern":"try {\n  const pageCount = countRenderedPdfPages(pdfBuffer);\n} catch (err) {\n  if (err.message.includes('page tree')) {\n    console.error('Unparseable or empty PDF — check Chromium/Playwright render output, retry, or inspect with pdfinfo.');\n    throw err; // or fall back to re-rendering\n  }\n  throw err;\n}","preventionTips":["Keep Playwright/Chromium updated so the PDF writer layout stays compatible with the parser","Assert the render step returned a non-empty %PDF- buffer before counting pages","Retain the failing PDF to disk for offline inspection with pdfinfo/qpdf","Retry once on transient render failures before surfacing the error"],"tags":["pdf","parsing","playwright"],"backgroundTag":"unexpected-response-shape","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}