odysseus-dev/odysseus · error · HTTPException

Failed to update document: {e}

Error message

Failed to update document: {e}

What it means

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.

Source

Thrown at routes/document/document_routes.py:693

                    id=str(uuid.uuid4()),
                    document_id=doc_id,
                    version_number=new_ver,
                    content=incoming_content,
                    summary=req.summary or "Manual edit",
                    source="user",
                )
                doc.version_count = new_ver
                db.add(ver)

            doc.current_content = incoming_content
            db.commit()
            db.refresh(doc)
            return _doc_to_dict(doc)
        except HTTPException:
            raise
        except Exception as e:
            db.rollback()
            raise HTTPException(500, f"Failed to update document: {e}")
        finally:
            db.close()

    # ---- PATCH /api/document/{doc_id} — metadata only ----
    @router.patch("/api/document/{doc_id}")
    async def patch_document(request: Request, doc_id: str, req: DocumentPatch) -> Dict[str, Any]:
        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)
            if req.title is not None:
                doc.title = req.title
            if req.language is not None:
                doc.language = req.language
            if req.session_id is not None:

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Read the interpolated exception and the server traceback for the failing stage.
  2. For lock timeouts, serialize writes per document or move off SQLite for concurrent use.
  3. Run migrations so DocumentVersion/Document columns match the models.
  4. Reproduce with the exact content payload if the email-coercion path is implicated.

Example fix

# before
await api.put(`/api/document/${id}`, { content }); // 500 Failed to update document: database is locked
# after
# single-writer queue per doc id, e.g. in the frontend:
await saveQueue.enqueue(id, () => api.put(`/api/document/${id}`, { content }));
Defensive patterns

Strategy: retry

Try / catch

try { await api.put(`/api/document/${id}`, { content }); }
catch (e) {
  if (e.status === 500 && /Failed to update document/.test(e.message)) {
    if (/locked|timeout/i.test(e.message)) return retryWithBackoff(() => api.put(`/api/document/${id}`, { content }), 3);
    notify('Save failed: ' + e.message);
  } else throw e;
}

Prevention

When it happens

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

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

Related errors


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