{"record":{"id":"ebd13219d9a8f1d6","repo":"mozilla/pdf.js","slug":"pages-tree-contains-circular-reference","errorCode":null,"errorMessage":"Pages tree contains circular reference.","messagePattern":"Pages tree contains circular reference\\.","errorType":"exception","errorClass":"FormatError","httpStatus":null,"severity":"error","filePath":"src/core/catalog.js","lineNumber":1343,"sourceCode":"    const xref = this.xref,\n      pageKidsCountCache = this.pageKidsCountCache,\n      pageIndexCache = this.pageIndexCache,\n      pageDictCache = this.pageDictCache;\n    let currentPageIndex = 0;\n\n    while (nodesToVisit.length) {\n      const currentNode = nodesToVisit.pop();\n\n      if (currentNode instanceof Ref) {\n        const count = pageKidsCountCache.get(currentNode);\n        // Skip nodes where the page can't be.\n        if (count >= 0 && currentPageIndex + count <= pageIndex) {\n          currentPageIndex += count;\n          continue;\n        }\n        // Prevent circular references in the /Pages tree.\n        if (visitedNodes.has(currentNode)) {\n          throw new FormatError(\"Pages tree contains circular reference.\");\n        }\n        visitedNodes.put(currentNode);\n\n        const obj = await (pageDictCache.get(currentNode) ||\n          xref.fetchAsync(currentNode));\n        if (obj instanceof Dict) {\n          let type = obj.getRaw(\"Type\");\n          if (type instanceof Ref) {\n            type = await xref.fetchAsync(type);\n          }\n          if (isName(type, \"Page\") || !obj.has(\"Kids\")) {\n            // Cache the Page reference, since it can *greatly* improve\n            // performance by reducing redundant lookups in long documents\n            // where all nodes are found at *one* level of the tree.\n            if (!pageKidsCountCache.has(currentNode)) {\n              pageKidsCountCache.put(currentNode, 1);\n            }\n            // Help improve performance of the `getPageIndex` method.","sourceCodeStart":1325,"sourceCodeEnd":1361,"githubUrl":"https://github.com/mozilla/pdf.js/blob/5903d58d58e4dd9ce6ffa3834aea8480f06b4ada/src/core/catalog.js#L1325-L1361","documentation":"Thrown in Catalog.getPageDict() (catalog.js:1343) when the top-down traversal of the /Pages tree encounters an indirect Ref that is already in the visitedNodes RefSet — i.e., the page tree contains a cycle. PDF page trees must be acyclic; a loop would hang the traversal, so PDF.js detects and rejects it.","triggerScenarios":"pdfDocument.getPage(n) triggers getPageDict, which walks Kids references; a node's Kids array (directly or transitively) points back to an ancestor Ref already in visitedNodes. Common in malformed PDFs where a /Pages node lists itself or a parent as a child.","commonSituations":"A buggy PDF producer that wrote a self-referential or cyclic Kids array; a corrupt page tree after a bad merge/split operation; an edited PDF with duplicated refs.","solutions":["Repair the PDF with 'mutool clean in.pdf out.pdf' or 'qpdf --linearize' to rebuild a clean, acyclic page tree.","If you produce PDFs, ensure /Pages /Kids never references an ancestor (validate the tree before writing).","Load with ignoreErrors/recovery and accept that the cyclic page may be skipped.","Fall back to pdfDocument.getAllPageDicts (the recovery-mode eager traversal) if available, which tolerates more breakage."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// You cannot pre-walk the page tree via public API cheaply.\n// Best prevention: repair known-cyclic PDFs before loading.\n// qpdf --linearize in.pdf out.pdf  (rebuilds page tree)","typeGuard":null,"tryCatchPattern":"try {\n  const page = await pdf.getPage(n);\n} catch (err) {\n  if (/circular reference/i.test(err?.message)) {\n    // try recovery traversal or report the broken page tree\n    console.warn('Page tree has a cycle; document needs repair', err);\n  } else throw err;\n}","preventionTips":["Repair cyclic page trees with qpdf/mutool before loading.","When producing PDFs, never let a /Pages /Kids reference an ancestor.","Run 'qpdf --check' to detect cycles pre-runtime.","Enable ignoreErrors so PDF.js can attempt recovery."],"tags":["pdf-structure","pages-tree","corrupt-pdf","circular-reference","page-retrieval"],"backgroundTag":null,"analyzedSha":"5903d58d58e4dd9ce6ffa3834aea8480f06b4ada","analyzedAt":"2026-08-13T02:28:27.364Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}