{"record":{"id":"e2cf673c7ead8401","repo":"odysseus-dev/odysseus","slug":"str-e-e2cf67","errorCode":null,"errorMessage":"str(e)","messagePattern":"str\\(e\\)","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":732,"sourceCode":"                    _get_session_or_404(db, req.session_id, user)\n                doc.session_id = req.session_id if req.session_id else None\n                if not req.session_id:\n                    # Tab closed / doc detached from its session — drop the\n                    # in-memory active-doc pointer so the last-resort injection\n                    # path doesn't re-surface this doc in a later chat (#1160).\n                    try:\n                        from src.agent_tools.document_tools import clear_active_document\n                        clear_active_document(doc_id)\n                    except Exception as e:\n                        logger.warning(\"Failed to clear active document %r on detach\", doc_id, exc_info=e)\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, str(e))\n        finally:\n            db.close()\n\n    # ---- DELETE /api/document/{doc_id} — soft delete ----\n    @router.delete(\"/api/document/{doc_id}\")\n    async def delete_document(request: Request, doc_id: str) -> Dict[str, str]:\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            doc.is_active = False\n            # Closed/deleted — drop the in-memory active-doc pointer so it isn't\n            # re-injected into a later, unrelated chat (#1160).\n            try:\n                from src.agent_tools.document_tools import clear_active_document","sourceCodeStart":714,"sourceCodeEnd":750,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L714-L750","documentation":"Bare 500 wrapper on PATCH /api/document/{doc_id}: any non-HTTP exception during metadata update (title/language change, session re-link via _get_session_or_404 internals, commit) becomes HTTPException(500, str(e)). Unlike sibling handlers it does not prefix the message, so the response body is just the raw exception text — useful for diagnosis but prone to leaking internals.","triggerScenarios":"Commit failure (lock, constraint on language/session_id length); invalid session_id taking an unexpected code path; None values slipping through the Pydantic DocumentPatch model into DB columns with constraints.","commonSituations":"Long language strings or invalid enum values accepted by the model but rejected by the DB; concurrent PATCHes on SQLite; session linkage pointing at a deleted session in a way that raises instead of 404ing.","solutions":["Read the response body — it contains the raw exception message from the failing stage.","Add length/format validation on title/language/session_id in the DocumentPatch model so bad values 422 instead of 500.","For lock errors, avoid concurrent PATCHes to the same doc or switch DB.","Consider prefixing the message like the other handlers and confirming no sensitive internals leak to clients."],"exampleFix":"# before\nclass DocumentPatch(BaseModel):\n    title: Optional[str] = None\n    language: Optional[str] = None  # any string -> DB error -> 500 str(e)\n# after\nclass DocumentPatch(BaseModel):\n    title: Optional[constr(max_length=200)] = None\n    language: Optional[Literal['markdown','python','pdf','email','text']] = None","handlingStrategy":"validation","validationCode":"const okTitle = (t) => typeof t === 'string' && t.length <= 200;\nconst okLang = (l) => ['markdown','python','pdf','email','text'].includes(l);\nif (patch.title && !okTitle(patch.title)) throw new Error('bad title');\nif (patch.language && !okLang(patch.language)) throw new Error('bad language');\nawait api.patch(`/api/document/${id}`, patch);","typeGuard":"const LANGS = new Set(['markdown','python','pdf','email','text']);\nfunction isSupportedLanguage(v: unknown): v is string {\n  return typeof v === 'string' && LANGS.has(v);\n}","tryCatchPattern":"try { await api.patch(`/api/document/${id}`, patch); }\ncatch (e) {\n  if (e.status === 500) { notify('Update failed: ' + e.body?.detail); return false; } // body carries raw cause\n  throw e;\n}","preventionTips":["Constrain title length and language enum in the request model so bad input 422s instead of 500ing","Validate PATCH payloads client-side","Note that this handler leaks raw exception text — parse it for diagnosis but never display it verbatim to end users"],"tags":["http-500","validation","patch","error-leak","database"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}