fix(workspace/chat_uploads): surface exception class + detail in 400 response #1575
Reference in New Issue
Block a user
Delete Branch "fix/chat-uploads-surface-exception-in-400"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Surface exception class +
str(exc)in the 400 JSON response fromPOST /internal/chat/uploads/ingest. Top-levelerrorkey preserved for backwards-compat with canvas / alert rules.Why
Hermes workspace PDF upload (forensic
a78762a0, 2026-05-19) returned the opaque{"error": "failed to parse multipart form"}only. Triage took ~25 min because the response carried no information about WHICH exception class fired or WHY the parser bailed. The underlying cause was a missingpython-multipartdep in the PyPI runtime (fixed inmolecule-ai-workspace-runtime#18).Surfacing
exc.class + str(exc)would have cut triage to ~10 min.Per
feedback_surface_actionable_failure_reason_to_user(CTO 2026-05-17):Salvage note re mc#1524 (closed, wrong-RCA)
mc#1524 attributed the 400 to Starlette's
max_part_sizelimit and proposed bumping it. That diagnosis was incorrect — Starlette only enforcesmax_part_sizeon form FIELDS (text values), not on file PARTS, so a 5 MB PDF would not trip that limit regardless of the value.The one useful idea from mc#1524 — surfacing the failure reason to the caller — is salvaged here as a separate, narrowly-scoped change with a unit test pinning the response shape.
Test
Adds
test_malformed_multipart_returns_exception_class_and_detailwhich sends a boundary-mismatched body, asserts 400, and pins the response shape (error/exception/detail keys present).Local run:
Verification path for CTO
After this + the runtime dep PR merge:
0.1.18in Chloe-Hermes.boundary=...) → expect 400 withexception+detailkeys.Companions
molecule-ai-workspace-runtime#18— the REAL fix (pin the dep).Reviewers
Standard 3-reviewer relay (core-qa team-gate required per
feedback_molecule_core_qa_review_team_required).Five-axis pass.
Correctness: workspace/internal_chat_uploads.py ingest_handler now returns exception class + str(exc) alongside the legacy 'error' key on multipart parse failure (400). Backwards-compatible: top-level 'error' string is unchanged so canvas / alert rules still match. Test test_malformed_multipart_returns_exception_class_and_detail pins the new shape with a real boundary mismatch that exercises Starlette's MultiPartException path.
Readability: Inline comment cites the forensic that motivated the change (a78762a0, 25min -> 10min triage) and links feedback_surface_actionable_failure_reason_to_user. Test docstring documents the same.
Architecture: Aligned with CTO 2026-05-17 #211 - user-facing failures MUST tell the user WHY (feedback_surface_actionable_failure_reason_to_user).
Security: type(exc).name is a safe string; str(exc) could in principle echo a filename/multipart-boundary fragment, but Starlette's MultiPartException carries protocol-level descriptions only (no body bytes / no credentials). No new attack surface.
Performance: N/A - error path only.
CI: all-required green on head
5f6aa3d.Security-axis pass.
Concern reviewed: surfacing str(exc) to a 400 response body in chat_uploads ingest could leak internal state. Checked:
The diagnostic gain (CTO 2026-05-17 directive: actionable failure reason) outweighs the marginal leak surface. Approved.
QA-axis pass (per feedback_molecule_core_qa_review_team_required).
Test test_malformed_multipart_returns_exception_class_and_detail:
CI evidence: CI/all-required green on head 5f6aa3d; CI/Python Lint & Test passed; qa-review/approved + security-review/approved are review-gate contexts that will flip once this APPROVE + core-security APPROVE land.
Approved.