{"record":{"id":"67ea944c9449283e","repo":"calcom/cal.diy","slug":"ics-feed-not-connected","errorCode":null,"errorMessage":"Ics Feed not connected.","messagePattern":"Ics Feed not connected\\.","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/platform/calendars/services/ics-feed.service.ts","lineNumber":106,"sourceCode":"      ICS_CALENDAR_TYPE,\n      userId\n    );\n\n    if (!icsFeedCredentials) {\n      throw new BadRequestException(\"Credentials for Ics Feed calendar not found.\");\n    }\n\n    if (icsFeedCredentials.invalid) {\n      throw new BadRequestException(\"Invalid Ics Feed credentials.\");\n    }\n\n    const { connectedCalendars } = await this.calendarsService.getCalendars(userId);\n    const icsCalendar = connectedCalendars.find(\n      (cal: { integration: { type: string } }) => cal.integration.type === ICS_CALENDAR_TYPE\n    );\n\n    if (!icsCalendar) {\n      throw new UnauthorizedException(\"Ics Feed not connected.\");\n    }\n    if (icsCalendar.error?.message) {\n      throw new UnauthorizedException(icsCalendar.error?.message);\n    }\n\n    return {\n      status: SUCCESS_STATUS,\n    };\n  }\n}\n","sourceCodeStart":88,"sourceCodeEnd":117,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/calendars/services/ics-feed.service.ts#L88-L117","documentation":"After the ICS credential is found and not flagged invalid, IcsFeedService.check calls calendarsService.getCalendars(userId) and searches connectedCalendars for an entry whose integration.type === ICS_CALENDAR_TYPE. If none is present, the credential exists but is not wired into the user's connected-calendars list, so the request is treated as 'never connected' and UnauthorizedException('Ics Feed not connected.') is thrown (HTTP 401, not 400, because the discrepancy implies a session/state problem).","triggerScenarios":"The credential row exists but the connected-calendar linkage row (selectedCalendars / destinationCalendars) was deleted or never created; a partial save that persisted the credential but failed before linking; the cache is stale and getCalendars returns a pre-add snapshot.","commonSituations":"A previous save partially failed between upsertUserAppCredential and the calendar-linking step; the connectedCalendars cache still serves a pre-add snapshot (note save() calls calendarsCacheService.deleteConnectedAndDestinationCalendarsCache, so a cache that wasn't cleared is the tell); user is on a different account whose credential exists but isn't linked.","solutions":["Re-run POST /v2/calendars/ics/save to re-create both the credential and the calendar linkage in one transactional step.","If the issue persists, force-bust the connected-calendars cache for that userId (the save path deletes it; a manual cache flush rules out stale reads).","Check that the user calling /check is the same user (and same team context) that owns the linked calendar."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const { connectedCalendars } = await calendarsService.getCalendars(userId);\nconst linked = connectedCalendars.some(c => c.integration.type === ICS_CALENDAR_TYPE);\nif (!linked) {\n  // re-run /save to recreate the calendar linkage\n}","typeGuard":null,"tryCatchPattern":"try {\n  await icsFeedService.check(userId);\n} catch (e) {\n  if (e instanceof UnauthorizedException && e.message === 'Ics Feed not connected.') {\n    await icsFeedService.save(userId, userEmail, urls); // relink\n  } else throw e;\n}","preventionTips":["If a save partially fails, re-save rather than calling /check.","Bust the connected-calendars cache for the user after a save.","Keep the userId consistent between save and check."],"tags":["ics","calendar","state","api-v2"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}