odysseus-dev/odysseus · error · HTTPException
Failed to list calendars
Error message
Failed to list calendars
What it means
Generic HTTP 500 from GET /calendars. The handler lazily ensures a default calendar exists (which commits), then queries all CalendarCal rows for the owner. Any exception outside HTTPException — default-calendar insert failing, query error, commit conflict — is rolled back, logged as 'Failed to list calendars: <e>', and returned as this 500.
Source
Thrown at routes/calendar_routes.py:1135
async def list_calendars(request: Request):
owner = _require_user(request)
db = SessionLocal()
try:
_ensure_default_calendar(db, owner)
# Listing calendars intentionally lazily creates a durable default.
# Other callers commit it with the event they are creating.
db.commit()
cals = db.query(CalendarCal).filter(CalendarCal.owner == owner).all()
return {"calendars": [
{"name": c.name, "href": c.id, "color": c.color, "source": c.source}
for c in cals
]}
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error("Failed to list calendars: %s", e)
raise HTTPException(500, "Failed to list calendars")
finally:
db.close()
@router.get("/events")
async def list_events(request: Request, start: str, end: str, calendar: str = ""):
owner = _require_user(request)
try:
start_dt = _parse_dt(start)
end_dt = _parse_dt(end)
except ValueError:
# A malformed range (e.g. a stray "NaN-NaN-NaN" from the client)
# shouldn't spam the user with an error notification on every poll —
# just log it and return no events for this window.
logger.warning("list_events: unparseable range start=%r end=%r", start, end)
return {"events": []}
db = SessionLocal()
try:
# Scope events to calendars owned by the caller.View on GitHub (pinned to f9235ebbf1)
Solutions
- Read the logged exception ('Failed to list calendars: <e>') to identify the true failure — insert race vs query error.
- For an insert race on the default calendar, retry the GET once; after the winner commits, the loser's second attempt succeeds.
- Apply pending schema migrations if the query itself is failing.
- Reduce concurrent calendar polling or enable SQLite WAL to cut lock contention.
Defensive patterns
Strategy: retry
Try / catch
try { return await api.get('/calendars'); }
catch (e) {
if (e.status === 500) return await api.get('/calendars'); // default-calendar insert race resolves on retry
throw e;
} Prevention
- Retry GET /calendars once on 500 — the lazy default-calendar creation race is self-healing.
- Avoid multiple tabs polling a brand-new account simultaneously.
- Enable WAL on SQLite (or use a server DB) to reduce lock-induced 500s.
When it happens
Trigger: Unique-constraint violation on the lazily inserted default calendar when two concurrent GET /calendars requests race to create it; DB schema mismatch after an upgrade; database file locked or unavailable; corrupt owner column data.
Common situations: Two tabs polling /calendars on a fresh account simultaneously, both trying to insert the default calendar; migration not run so CalendarCal lacks a column the query references; SQLite contention with the CalDAV sync worker writing at the same moment.
Related errors
- Failed to delete calendar
- Failed to create calendar
- Failed to update calendar
- Failed to create document: {e}
- Failed to fetch document library: {e}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/5384289a4525892c.
Report an issue: GitHub.