{"record":{"id":"e8cdfe9a6aab2157","repo":"odysseus-dev/odysseus","slug":"failed-to-import-ics","errorCode":null,"errorMessage":"Failed to import ICS","messagePattern":"Failed to import ICS","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"routes/calendar_routes.py","lineNumber":1557,"sourceCode":"                )\n                db.add(ev)\n                imported += 1\n\n            db.commit()\n            return {\n                \"ok\": True,\n                \"imported\": imported,\n                \"skipped\": skipped,\n                \"repaired\": repaired,\n                \"calendar\": cal_display,\n                \"calendar_id\": target_cal.id,\n            }\n        except HTTPException:\n            raise\n        except Exception as e:\n            db.rollback()\n            logger.error(\"Failed to import ICS: %s\", e)\n            raise HTTPException(500, \"Failed to import ICS\")\n        finally:\n            db.close()\n\n    @router.get(\"/export/{cal_id}\")\n    async def export_ics(request: Request, cal_id: str):\n        \"\"\"Export a calendar as .ics file.\"\"\"\n        from fastapi.responses import Response\n\n        owner = _require_user(request)\n        db = SessionLocal()\n        try:\n            cal = _get_or_404_calendar(db, cal_id, owner)\n            events = db.query(CalendarEvent).filter(\n                CalendarEvent.calendar_id == cal_id,\n                CalendarEvent.status != \"cancelled\",\n            ).all()\n\n            lines = [","sourceCodeStart":1539,"sourceCodeEnd":1575,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/calendar_routes.py#L1539-L1575","documentation":"Generic HTTP 500 from POST /import. After the ICS parses successfully, the handler finds or creates the target calendar and imports events (with skip/repair logic) inside a transaction. Any non-HTTP exception during that phase — DB commit failure, a VEVENT with fields that break the row-building code, timezone resolution failure — is rolled back, logged as 'Failed to import ICS: <e>', and returned as this 500. Parse failures of the file itself are the separate 400 'Invalid ICS file' error.","triggerScenarios":"A VEVENT whose DTSTART timezone cannot be resolved (missing tzdata, nonstandard TZID); a property type the import code does not handle (PERIOD duration values, weird escaping); database locked at commit by the CalDAV worker; constraint violation when inserting an imported event.","commonSituations":"Importing exports from niche calendar clients with nonstandard TZIDs; large imports colliding with background sync on SQLite; tzdata not installed in a slim container so timezone lookups fail per-event.","solutions":["Read the server log ('Failed to import ICS: <e>') — it identifies the event property or DB operation that failed.","If the error is timezone-related, install tzdata (pip install tzdata / system zoneinfo) in the runtime.","Split the ICS file and import halves to isolate the offending VEVENT, then fix or drop it.","For lock contention, import when the sync worker is idle or enable WAL."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const text = await file.text();\nif (!/BEGIN:VCALENDAR/.test(text)) throw new Error('Invalid ICS'); // avoids the 400 variant\nif (file.size > 10 * 1024 * 1024) throw new Error('File exceeds 10 MB limit'); // avoids the size error","typeGuard":null,"tryCatchPattern":"try { await api.post('/import', formData); }\ncatch (e) {\n  if (e.status === 500) {\n    // parse succeeded; a specific VEVENT broke import — check log, split file to isolate\n    throw e;\n  }\n  if (e.status === 400) showError(e.detail);\n  else throw e;\n}","preventionTips":["Ensure tzdata is installed in the deployment image before importing timezone-heavy exports.","On 500, bisect the ICS file (import halves) to find the offending VEVENT.","Import when background CalDAV sync is idle to avoid commit contention."],"tags":["http-500","ics","icalendar","database","fastapi"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}