{"record":{"id":"2c5c3c623f4fda1b","repo":"odysseus-dev/odysseus","slug":"failed-to-update-document-e","errorCode":null,"errorMessage":"Failed to update document: {e}","messagePattern":"Failed to update document: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":693,"sourceCode":"                    id=str(uuid.uuid4()),\n                    document_id=doc_id,\n                    version_number=new_ver,\n                    content=incoming_content,\n                    summary=req.summary or \"Manual edit\",\n                    source=\"user\",\n                )\n                doc.version_count = new_ver\n                db.add(ver)\n\n            doc.current_content = incoming_content\n            db.commit()\n            db.refresh(doc)\n            return _doc_to_dict(doc)\n        except HTTPException:\n            raise\n        except Exception as e:\n            db.rollback()\n            raise HTTPException(500, f\"Failed to update document: {e}\")\n        finally:\n            db.close()\n\n    # ---- PATCH /api/document/{doc_id} — metadata only ----\n    @router.patch(\"/api/document/{doc_id}\")\n    async def patch_document(request: Request, doc_id: str, req: DocumentPatch) -> Dict[str, Any]:\n        user = get_current_user(request)\n        db = SessionLocal()\n        try:\n            doc = db.query(Document).filter(Document.id == doc_id).first()\n            if not doc:\n                raise HTTPException(404, \"Document not found\")\n            _verify_doc_owner(db, doc, user)\n            if req.title is not None:\n                doc.title = req.title\n            if req.language is not None:\n                doc.language = req.language\n            if req.session_id is not None:","sourceCodeStart":675,"sourceCodeEnd":711,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L675-L711","documentation":"Generic 500 wrapper on PUT /api/document/{doc_id}: any unexpected exception during the update flow (email-content coercion, version coalescing, DocumentVersion insert, commit) is rolled back and re-raised as 'Failed to update document: <e>'. Real HTTPExceptions (e.g. the 404 or ownership 403) pass through untouched.","triggerScenarios":"DB failure during commit (constraint, lock, connection loss); exception in _coerce_email_document_content on unusual content; version-row insert violating a constraint; schema drift in DocumentVersion columns.","commonSituations":"Concurrent edits from two tabs causing lock errors on SQLite; unmigrated DocumentVersion table; malformed content payloads hitting an unguarded branch in the email coercion helper.","solutions":["Read the interpolated exception and the server traceback for the failing stage.","For lock timeouts, serialize writes per document or move off SQLite for concurrent use.","Run migrations so DocumentVersion/Document columns match the models.","Reproduce with the exact content payload if the email-coercion path is implicated."],"exampleFix":"# before\nawait api.put(`/api/document/${id}`, { content }); // 500 Failed to update document: database is locked\n# after\n# single-writer queue per doc id, e.g. in the frontend:\nawait saveQueue.enqueue(id, () => api.put(`/api/document/${id}`, { content }));","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await api.put(`/api/document/${id}`, { content }); }\ncatch (e) {\n  if (e.status === 500 && /Failed to update document/.test(e.message)) {\n    if (/locked|timeout/i.test(e.message)) return retryWithBackoff(() => api.put(`/api/document/${id}`, { content }), 3);\n    notify('Save failed: ' + e.message);\n  } else throw e;\n}","preventionTips":["Serialize saves per document (single autosave queue)","Retry transient lock/timeout failures with backoff","Keep local drafts so a failed save never loses user input"],"tags":["http-500","sqlalchemy","concurrency","update","database"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}