Repository navigation
Conversation
Deterministic parse failures on corrupt uploads (truncated OLE files raising olefile.olefile.OleFileError, truncated OOXML packages raising zipfile.BadZipFile, invalid spreadsheets raising unstructured.errors.UnprocessableEntityError) were falling through the OSError/ValueError handlers to the app-level catch-all and returning 500. Since olefile's OleFileError subclasses OSError, the new clause must run before the OSError handler. These are client-side problems: 400 with a descriptive detail, so callers can classify them as bad input rather than transient server errors and retry. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Deterministic content-parse failures on corrupt uploads currently return HTTP 500 instead of a 4xx:
.doc,.ppt,.msg):olefile.olefile.OleFileError(e.g.OLE directory index out of range,incomplete OLE sector).pptxetc.):zipfile.BadZipFile(File is not a zip file)unstructured.errors.UnprocessableEntityError(Not a valid XLSX file.)These escape the
except OSError/except ValueErrorhandlers inpipeline_apiand fall through to the app-level catch-all inapp.py, which returns500 {"detail": str(e)}.This matters for downstream consumers that classify failures by status class: 4xx means "bad input, don't retry", 5xx means "transient, retry". A corrupt document that can never parse gets retried as if the server were at fault, and user-facing errors get mislabeled as server failures.
Root cause
The app-level
Exceptionhandler inapp.pyis the only net for these exception types; nothing inpipeline_apimaps them to client errors.Fix
Add three type-based exception handlers before the existing
except OSErrorblock inpipeline_api(ordering matters:OleFileErrorsubclassesOSError, so it must be caught first):olefile.olefile.OleFileError-> 400 (File does not appear to be a valid OLE file: ...)zipfile.BadZipFile-> 400 (File does not appear to be a valid OOXML file: ...)unstructured.errors.UnprocessableEntityError-> 400 (detail passes through)olefileis added as an explicit dependency inpyproject.tomlsince it is now imported directly (it was previously only transitive viaunstructured). Note the submodule import (from olefile.olefile import OleFileError): olefile 0.47 does not re-export the exception classes at the package level.What still returns 500
Kept deliberately out of scope, flagged here for maintainers:
ValueError: bytes length not a multiple of item size(fromarray/structinside olefile on degenerate OLE files) - it escapesOleFileIOas a bareValueError, whichpartition()also raises for many legitimate reasons, so catching it by type is not safe and an allowlist of message substrings would be brittle.'NoneType' object has no attribute 'inner_content_elements'from python-docx on broken.docxfiles - anAttributeError, not cleanly catchable by type. (Truncated.docxfiles already return 422 via the existingnot a ZIP archive (so not a DOCX file)ValueError branch, so the common docx truncation case is covered.)Everything else (genuine server errors, OOM, model failures) continues to return 500.
Test evidence
test_general/api/test_app.py::test_truncated_binary_documents_return_400uploads half ofsample-docs/fake.doc(OLE) and half ofsample-docs/fake-power-point.pptx(OOXML zip) and asserts 400. Both tests fail againstmain(500 from the catch-all) and pass with this change. Ran locally withuv run pytest test_general/api/test_app.py -k truncated: 2 passed.