odysseus-dev/odysseus · error · HTTPException

str(e)

Error message

str(e)

What it means

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.

Source

Thrown at routes/document/document_routes.py:732

                    _get_session_or_404(db, req.session_id, user)
                doc.session_id = req.session_id if req.session_id else None
                if not req.session_id:
                    # Tab closed / doc detached from its session — drop the
                    # in-memory active-doc pointer so the last-resort injection
                    # path doesn't re-surface this doc in a later chat (#1160).
                    try:
                        from src.agent_tools.document_tools import clear_active_document
                        clear_active_document(doc_id)
                    except Exception as e:
                        logger.warning("Failed to clear active document %r on detach", doc_id, exc_info=e)
            db.commit()
            db.refresh(doc)
            return _doc_to_dict(doc)
        except HTTPException:
            raise
        except Exception as e:
            db.rollback()
            raise HTTPException(500, str(e))
        finally:
            db.close()

    # ---- DELETE /api/document/{doc_id} — soft delete ----
    @router.delete("/api/document/{doc_id}")
    async def delete_document(request: Request, doc_id: str) -> Dict[str, str]:
        user = get_current_user(request)
        db = SessionLocal()
        try:
            doc = db.query(Document).filter(Document.id == doc_id).first()
            if not doc:
                raise HTTPException(404, "Document not found")
            _verify_doc_owner(db, doc, user)
            doc.is_active = False
            # Closed/deleted — drop the in-memory active-doc pointer so it isn't
            # re-injected into a later, unrelated chat (#1160).
            try:
                from src.agent_tools.document_tools import clear_active_document

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Read the response body — it contains the raw exception message from the failing stage.
  2. Add length/format validation on title/language/session_id in the DocumentPatch model so bad values 422 instead of 500.
  3. For lock errors, avoid concurrent PATCHes to the same doc or switch DB.
  4. Consider prefixing the message like the other handlers and confirming no sensitive internals leak to clients.

Example fix

# before
class DocumentPatch(BaseModel):
    title: Optional[str] = None
    language: Optional[str] = None  # any string -> DB error -> 500 str(e)
# after
class DocumentPatch(BaseModel):
    title: Optional[constr(max_length=200)] = None
    language: Optional[Literal['markdown','python','pdf','email','text']] = None
Defensive patterns

Strategy: validation

Validate before calling

const okTitle = (t) => typeof t === 'string' && t.length <= 200;
const okLang = (l) => ['markdown','python','pdf','email','text'].includes(l);
if (patch.title && !okTitle(patch.title)) throw new Error('bad title');
if (patch.language && !okLang(patch.language)) throw new Error('bad language');
await api.patch(`/api/document/${id}`, patch);

Type guard

const LANGS = new Set(['markdown','python','pdf','email','text']);
function isSupportedLanguage(v: unknown): v is string {
  return typeof v === 'string' && LANGS.has(v);
}

Try / catch

try { await api.patch(`/api/document/${id}`, patch); }
catch (e) {
  if (e.status === 500) { notify('Update failed: ' + e.body?.detail); return false; } // body carries raw cause
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/e2cf673c7ead8401. Report an issue: GitHub.