odysseus-dev/odysseus · error · HTTPException

Failed to export ICS

Error message

Failed to export ICS

What it means

Generic HTTP 500 from GET /export/{cal_id}. The handler loads the calendar via _get_or_404_calendar (404 for missing/foreign ids, re-raised untouched), then serializes its events into an ICS payload and wraps it in a text/calendar Response. Any non-HTTP exception during serialization — an event field that breaks ICS generation, timezone rendering failure, or a DB error mid-query — is logged as 'Failed to export ICS: <e>' and returned as this 500.

Source

Thrown at routes/calendar_routes.py:1615

                    lines.append(f"RRULE:{ev.rrule}")
                lines.append("END:VEVENT")
            lines.append("END:VCALENDAR")

            ics_data = "\r\n".join(lines)
            download_name = _safe_ics_filename(cal.name)
            return Response(
                content=ics_data,
                media_type="text/calendar",
                headers={
                    "Content-Disposition": f'attachment; filename="{download_name}"',
                    "X-Content-Type-Options": "nosniff",
                },
            )
        except HTTPException:
            raise
        except Exception as e:
            logger.error("Failed to export ICS: %s", e)
            raise HTTPException(500, "Failed to export ICS")
        finally:
            db.close()

    @router.post("/quick-parse")
    async def quick_parse(request: Request):
        """Parse a natural-language event description into structured fields.

        Input: {"text": "lunch with sara friday 1pm downtown", "tz": "America/New_York"}
        Output: {"ok": true, "event": {"summary", "dtstart", "dtend",
                  "all_day", "location", "description"}, "confidence": 0.0-1.0}

        Anchored on the server's current date/time so phrases like
        "tomorrow", "next Tuesday", "in 30 minutes" resolve correctly.
        Uses the "utility" endpoint (small / fast model) to keep latency low.
        """
        owner = _require_user(request)
        from src.endpoint_resolver import resolve_endpoint
        from src.llm_core import llm_call_async

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check the server log ('Failed to export ICS: <e>') for the failing event or field.
  2. Inspect the calendar's events (GET /events for that range) for the odd-one-out — often a single event with unusual datetime/timezone data; fix or delete it.
  3. Ensure tzdata is available in the runtime if the error names a timezone lookup.
  4. Verify cal_id comes from the caller's own GET /calendars list (a bad id gives 404, not this 500).
Defensive patterns

Strategy: try-catch

Validate before calling

const cals = await (await fetch('/calendars')).json();
if (!cals.calendars.some(c => c.href === calId)) throw new NotFound('no such calendar'); // clean 404 path
window.location = `/export/${calId}`;

Try / catch

try { const blob = await api.get(`/export/${calId}`, {responseType: 'blob'}); saveBlob(blob); }
catch (e) {
  if (e.status === 404) showError('Calendar not found');
  else if (e.status === 500) showError('Export failed — server log names the bad event');
}

Prevention

When it happens

Trigger: An event with a stored datetime or timezone that the ICS writer cannot serialize (naive/aware mismatch, unknown TZID); a field containing characters the exporter cannot escape; database error while streaming the event rows; null in a column the export code assumes non-null.

Common situations: Exporting a calendar containing events created by an older version or by an import with edge-case data; tzdata missing in the deployment container; corrupt rows from a partially applied migration.

Related errors


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