{"record":{"id":"8933a6840d9a6fc3","repo":"docling-project/docling","slug":"pagepreprocessingmodel-returned-unexpected-number","errorCode":null,"errorMessage":"PagePreprocessingModel returned unexpected number of pages","messagePattern":"PagePreprocessingModel returned unexpected number of pages","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"docling/pipeline/standard_pdf_pipeline.py","lineNumber":529,"sourceCode":"            try:\n                if _log.isEnabledFor(logging.DEBUG):\n                    _t_start = time.time()\n                    _t_mono = time.monotonic()\n                pages = [page for _, page in valid]\n                processed_pages = list(\n                    self.model(valid[0][0].conv_res, pages)  # type: ignore[arg-type]\n                )\n                if _log.isEnabledFor(logging.DEBUG):\n                    _log.debug(\n                        \"PIPELINE_PROFILING Stage preprocess: run_id=%d pages=%s start=%.3f end=%.3f duration=%.3fs\",\n                        rid,\n                        [it.page_no for it, _ in valid],\n                        _t_start,\n                        time.time(),\n                        time.monotonic() - _t_mono,\n                    )\n                if len(processed_pages) != len(pages):\n                    raise RuntimeError(\n                        \"PagePreprocessingModel returned unexpected number of pages\"\n                    )\n                for idx, processed_page in enumerate(processed_pages):\n                    result.append(\n                        ThreadedItem(\n                            payload=processed_page,\n                            run_id=rid,\n                            page_no=valid[idx][0].page_no,\n                            conv_res=valid[idx][0].conv_res,\n                        )\n                    )\n            except Exception as exc:\n                _log.error(\n                    \"Stage preprocess failed for run %d, pages %s: %s\",\n                    rid,\n                    [it.page_no for it, _ in valid],\n                    exc,\n                    exc_info=False,","sourceCodeStart":511,"sourceCodeEnd":547,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/pipeline/standard_pdf_pipeline.py#L511-L547","documentation":"The preprocessing stage of StandardPdfPipeline runs PagePreprocessingModel over a batch of valid pages and asserts that the number of processed pages returned equals the number of pages submitted. A length mismatch means the preprocessing model changed the page count, breaking the ThreadedItem bookkeeping that maps results back to (run_id, page_no, conv_res). As with error 341, this is an internal invariant guard that fires when a custom or modified preprocessing model violates its contract.","triggerScenarios":"Overriding or replacing the preprocessing model so it filters, merges, or drops pages; a custom pipeline that injects its own PagePreprocessingModel subclass returning fewer results than input pages; version skew between docling-core model code and pipeline code.","commonSituations":"Adding a custom preprocessing step (e.g. blank-page removal) that skips pages instead of returning an empty processed page; running with locally patched docling-core where the model return shape changed; mixing docling and docling-core versions from different releases.","solutions":["Return one processed page per input page from the custom preprocessing model; mark unprocessed pages with an empty/failed payload rather than omitting them.","If page filtering is genuinely needed, do it before handing pages to the pipeline (filter conv_res.pages) rather than inside the model.","Align docling and docling-core versions (pip install -U docling docling-core) so the model contract matches.","Reproduce with stock pipeline options to confirm the customization is the cause."],"exampleFix":"# before\nclass MyPreprocessModel(PagePreprocessingModel):\n    def __call__(self, conv_res, pages):\n        return [p for p in pages if not is_blank(p)]  # drops pages -> mismatch\n\n# after\nclass MyPreprocessModel(PagePreprocessingModel):\n    def __call__(self, conv_res, pages):\n        return [empty_processed_page(p) if is_blank(p) else p for p in pages]","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    result = converter.convert(doc)\nexcept RuntimeError as e:\n    if 'unexpected number of pages' in str(e):\n        # preprocessing model changed page count; return 1:1 results instead\n        ...","preventionTips":["Filter pages before the pipeline, never inside a preprocessing model.","Custom preprocessing models must preserve the input page count.","Run with stock pipeline options first to isolate customization bugs."],"tags":["pipeline","preprocessing","custom-model","internal-invariant"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}