odysseus-dev/odysseus · error · HTTPException

Failed to update calendar

Error message

Failed to update calendar

What it means

Generic HTTP 500 from PUT /calendars/{cal_id}. The handler loads the calendar via _get_or_404_calendar (which itself raises 404 for missing/foreign calendars), applies optional name/color updates, and commits. Non-HTTP exceptions — commit failure, DB constraint, oversized values — are rolled back, logged as 'Failed to update calendar: <e>', and returned as this 500. The 404 path is re-raised untouched, so this error always means the update itself failed, not a bad id.

Source

Thrown at routes/calendar_routes.py:1402

    @router.put("/calendars/{cal_id}")
    async def update_calendar(request: Request, cal_id: str, name: str = None, color: str = None):
        owner = _require_user(request)
        _reserve_calendar_uploads(request, color)
        db = SessionLocal()
        try:
            cal = _get_or_404_calendar(db, cal_id, owner)
            if name is not None:
                cal.name = name
            if color is not None:
                cal.color = color
            db.commit()
            return {"ok": True}
        except HTTPException:
            raise
        except Exception as e:
            db.rollback()
            logger.error("Failed to update calendar: %s", e)
            raise HTTPException(500, "Failed to update calendar")
        finally:
            db.close()


    # Hard cap on ICS upload (ICS_MAX_BYTES, default 10 MB). Loading the whole
    # file into memory is unavoidable with python-icalendar, so an unbounded
    # upload would OOM.

    @router.post("/import")
    async def import_ics(request: Request, file: UploadFile = File(...), calendar_name: str = ""):
        """Import events from an .ics file (scoped to caller's account)."""
        from icalendar import Calendar as iCal

        owner = _require_user(request)
        db = SessionLocal()
        try:
            content = await read_upload_limited(file, ICS_MAX_BYTES, "ICS file")
            try:

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check the server log ('Failed to update calendar: <e>') for the concrete cause.
  2. Keep name/color short and well-formed (e.g. '#rrggbb' for color).
  3. For lock contention, retry once after other writes finish or enable SQLite WAL.
  4. Run schema migrations if the log reports a missing column.
Defensive patterns

Strategy: try-catch

Validate before calling

const patch = {};
if (name != null) patch.name = String(name).trim().slice(0, 100);
if (color != null && /^#[0-9a-fA-F]{6}$/.test(color)) patch.color = color;
await api.put(`/calendars/${calId}`, null, {params: patch});

Try / catch

try { await api.put(`/calendars/${calId}`, null, {params: {name, color}}); }
catch (e) {
  if (e.status === 404) refreshCalendarList(); // calendar gone — resync ids
  else if (e.status === 500) throw e;          // check log 'Failed to update calendar'
  else throw e;
}

Prevention

When it happens

Trigger: Setting name or color to a value the DB column rejects (excessive length, null bytes); database locked at commit; schema mismatch after upgrade; a color string not matching a CHECK constraint if one exists.

Common situations: Renaming to a very long pasted string; color picker emitting an unexpected format; concurrent CalDAV sync holding the write lock; migration not run so a referenced column is missing.

Related errors


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