Skip to content

fix: report document load failures instead of deferring them as -1 page count - #301

Merged
PeterStaar-IBM merged 1 commit into
docling-project:mainfrom
wittjeff:fix/report-load-failure
Jul 19, 2026
Merged

fix: report document load failures instead of deferring them as -1 page count#301
PeterStaar-IBM merged 1 commit into
docling-project:mainfrom
wittjeff:fix/report-load-failure

Conversation

@wittjeff

Copy link
Copy Markdown
Contributor

Fixes #239. Related: docling-project/docling#3031.

Problem

When qpdf cannot parse a document, pdf_decoder<DOCUMENT>::process_document_from_bytesio correctly catches the exception, logs it, and returns false — with number_of_pages still at its -1 sentinel. But every pybind loader discards that return value:

  • docling_parser::load_document returns true whenever the file merely exists;
  • docling_parser::load_document_from_bytesio returns true unless an exception escapes — which it never does, because the decoder catches internally;
  • both docling_threaded_base loaders have the same ignored return (their try/catch is equally unreachable for this failure).

So the Python load() — which does check the loader result and would raise — never gets the signal. The failure only surfaces later and indirectly:

parser = DoclingPdfParser()
doc = parser.load(path_or_stream=BytesIO(b"%PDF-1.4\ngarbage"))  # "succeeds"
doc.is_loaded()          # True
doc.number_of_pages()    # -1

Downstream, docling propagates the -1 as Inconsistent number of pages: N!=-1 and rejects the document via is_valid() (docling#3031). In the threaded parser it is quieter still: normalise_page_numbers(key, -1, None) silently produces an empty schedule — the corrupt document "loads" and processes zero pages with no error at all, and with an explicit page list the user gets the baffling Invalid page number 1 ... with -1 pages.

#232 (page count from getAllPages()) and #240 robustify counting for documents qpdf can open; this PR closes the remaining path, where qpdf cannot open the document at all.

Fix

All four loaders now check the decoder's result; on failure they erase the just-created decoder from the registry and return false:

  • a decoder that never parsed no longer stays registered (is_loaded() reporting true with number_of_pages() == -1);
  • the existing Python-side raise RuntimeError("Failed to load document with key ...") now fires at load time — a loud, immediate failure exactly where the caller can act on it (e.g. docling falling back to its lenient pypdfium2 backend, as discussed in docling#3031);
  • normalise_page_numbers additionally rejects a negative page count outright, so a -1 can never silently schedule an empty task list.

This is signalling-only: documents that qpdf can parse (including with warnings) load exactly as before. No groundtruth impact.

Tests

tests/test_load_failure.py: garbage bytes via BytesIO and via file path raise RuntimeError at load() for both DoclingPdfParser and DoclingThreadedPdfParser; a failed load leaves no stale key registered; minimal valid PDFs still load with the correct page count.

…ge count

When qpdf cannot parse a document, pdf_decoder<DOCUMENT> correctly returns
false with number_of_pages left at its -1 sentinel - but every pybind
loader discarded that return value and reported success. The failure only
surfaced downstream: is_loaded() true, number_of_pages() == -1, docling
logging 'Inconsistent number of pages: N!=-1' and rejecting the document
(docling-project#239, docling#3031). The threaded parser was quieter still, silently
scheduling zero pages from the -1 count.

All four loaders now check the decoder result, erase the never-parsed
decoder from the registry, and return false, so the existing Python-side
RuntimeError fires at load time. normalise_page_numbers additionally
rejects negative page counts outright. Documents that qpdf can parse load
exactly as before.

Fixes docling-project#239

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jeff Witt <152964771+witt3rd@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

DCO Check Passed

Thanks @wittjeff, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@PeterStaar-IBM PeterStaar-IBM left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@PeterStaar-IBM
PeterStaar-IBM requested a review from cau-git July 15, 2026 05:57
@PeterStaar-IBM

Copy link
Copy Markdown
Member

@cau-git can you have a quick look: I think it is a good solution, but need a second pair of eyes.

@PeterStaar-IBM
PeterStaar-IBM merged commit 77eab80 into docling-project:main Jul 19, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DoclingParseDocumentBackend.page_count() returns -1 when docling-parse fails to parse PDF page tree

3 participants