{"record":{"id":"a5a183576d5426c1","repo":"odysseus-dev/odysseus","slug":"failed-to-create-document-e","errorCode":null,"errorMessage":"Failed to create document: {e}","messagePattern":"Failed to create document: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":216,"sourceCode":"                summary=\"Initial version\",\n                source=\"user\",\n            )\n            db.add(doc)\n            db.add(ver)\n            db.commit()\n            db.refresh(doc)\n            try:\n                from src.event_bus import fire_event\n                fire_event(\"document_created\", doc.owner)\n            except Exception:\n                logger.debug(\"document_created event dispatch failed\", exc_info=True)\n            return _doc_to_dict(doc)\n        except HTTPException:\n            raise\n        except Exception as e:\n            db.rollback()\n            logger.error(f\"Failed to create document: {e}\")\n            raise HTTPException(500, f\"Failed to create document: {e}\")\n        finally:\n            db.close()\n\n    # ---- POST /api/documents/import-pdf ----\n    @router.post(\"/api/documents/import-pdf\")\n    async def import_pdf(\n        request: Request,\n        file: UploadFile = File(...),\n        session_id: Optional[str] = Form(None),\n    ) -> Dict[str, Any]:\n        \"\"\"Upload a PDF and create the matching Document.\n\n        Detects AcroForm fields — if any, creates a form-backed markdown doc\n        (clickable inputs in the PDF view). Otherwise creates a plain PDF doc\n        with a `pdf_source` marker so the viewer renders the pages without\n        overlays.\n        \"\"\"\n        from src.pdf_forms import has_form_fields, extract_fields","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L198-L234","documentation":"Generic 500 raised by POST /api/documents when any unexpected exception escapes the create-document flow (the except Exception wrapper after db.rollback()). The original exception is interpolated into the message, so the text after the colon is the real cause (usually a SQLAlchemy IntegrityError, a missing column, or a serialization failure in _doc_to_dict). HTTPException subclasses are re-raised untouched; everything else becomes a 500.","triggerScenarios":"POST /api/documents with a payload that violates a DB constraint (duplicate id, NOT NULL owner), a schema drift between the Document model and the actual table (column added in code but not migrated), or an exception thrown by db.commit()/db.refresh() inside the try block.","commonSituations":"Running the API against an older SQLite/Postgres database that was never migrated after model changes; concurrent creates hitting a unique constraint; an event-bus or session-id bug surfacing before the HTTPException re-raise guard.","solutions":["Read the server log line 'Failed to create document: <e>' — the interpolated exception is the actual root cause.","If it is an OperationalError/NoSuchColumnError, run the project's DB migration or recreate the dev database so the schema matches the Document model.","If it is an IntegrityError, inspect the request body for missing required fields (owner/session linkage) and fix the client payload.","Reproduce locally with the same payload and --log-level debug to get the full traceback."],"exampleFix":"// before\ncurl -X POST /api/documents -d '{\"title\":\"x\"}' # 500 Failed to create document: NOT NULL constraint failed\n// after\n# migrate the DB, then send a payload with all required fields\ncurl -X POST /api/documents -d '{\"title\":\"x\",\"language\":\"markdown\",\"content\":\"...\"}'","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const doc = await api.post('/api/documents', payload);\n} catch (e) {\n  if (e.status === 500 && /Failed to create document/.test(e.message)) {\n    // surface server detail; the suffix after the colon is the root cause\n    notify('Create failed: ' + e.message.split(': ').slice(1).join(': '));\n  } else throw e;\n}","preventionTips":["Validate required fields client-side before POST","Keep DB schema migrated before deploying new models","Never retry a 500 create blindly — the row may have partially committed"],"tags":["fastapi","sqlalchemy","http-500","database"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}