{"record":{"id":"c4ce1e51bc285639","repo":"calcom/cal.diy","slug":"apple-calendar-not-connected","errorCode":null,"errorMessage":"Apple calendar not connected.","messagePattern":"Apple calendar not connected\\.","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/platform/calendars/services/apple-calendar.service.ts","lineNumber":51,"sourceCode":"    const appleCalendarCredentials = await this.credentialRepository.findCredentialByTypeAndUserId(\n      APPLE_CALENDAR_TYPE,\n      userId\n    );\n\n    if (!appleCalendarCredentials) {\n      throw new BadRequestException(\"Credentials for apple calendar not found.\");\n    }\n\n    if (appleCalendarCredentials.invalid) {\n      throw new BadRequestException(\"Invalid apple calendar credentials.\");\n    }\n\n    const { connectedCalendars } = await this.calendarsService.getCalendars(userId);\n    const appleCalendar = connectedCalendars.find(\n      (cal: { integration: { type: string } }) => cal.integration.type === APPLE_CALENDAR_TYPE\n    );\n    if (!appleCalendar) {\n      throw new UnauthorizedException(\"Apple calendar not connected.\");\n    }\n    if (appleCalendar.error?.message) {\n      throw new UnauthorizedException(appleCalendar.error?.message);\n    }\n\n    return {\n      status: SUCCESS_STATUS,\n    };\n  }\n\n  async saveCalendarCredentials(userId: number, userEmail: string, username: string, password: string) {\n    if (!username || !password || username.length <= 1 || password.length <= 1) {\n      throw new BadRequestException(`Username or password cannot be empty`);\n    }\n\n    const existingAppleCalendarCredentials = await this.credentialRepository.getAllUserCredentialsByTypeAndId(\n      APPLE_CALENDAR_TYPE,\n      userId","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/calendars/services/apple-calendar.service.ts#L33-L69","documentation":"Thrown by AppleCalendarService.checkIfCalendarConnected (apple-calendar.service.ts:51) as UnauthorizedException (HTTP 401) when the credential row exists and is valid, but calendarsService.getCalendars(userId) returned a connectedCalendars array containing no entry whose integration.type === APPLE_CALENDAR_TYPE. The credential is stored but no Apple calendar is actually surfaced/linked by the integration layer.","triggerScenarios":"Credential saved but the link/selectedCalendars step never completed; CalendarsCacheService returns a stale cached list missing the apple entry (cache not invalidated after save); the integration returned calendars but none matched the type filter.","commonSituations":"Race between an in-flight save and a check; cache key stale because save didn't call deleteConnectedAndDestinationCalendarsCache; selectedCalendars row missing for the apple calendar.","solutions":["Invalidate the connected-calendars cache for the user (calendarsCacheService.deleteConnectedAndDestinationCalendarsCache) and retry check.","Confirm a SelectedCalendars row exists for the apple calendar externalId + credentialId.","Re-run the save flow, which calls dav.listCalendars() and upserts the credential, forcing the integration to re-link."],"exampleFix":"// before: check immediately after save returns 401 due to stale cache\nawait api.post('/v2/calendars/apple_calendar/save', { ... });\nawait api.get('/v2/calendars/apple_calendar/check'); // 401 'Apple calendar not connected.'\n\n// after: clear cache (server-side) or wait for invalidation, then check\ncalendarsCacheService.deleteConnectedAndDestinationCalendarsCache(userId);\nawait api.get('/v2/calendars/apple_calendar/check'); // 200","handlingStrategy":"validation","validationCode":"// Confirm the apple calendar appears in the (cache-busted) list before checking\nawait api.post('/v2/calendars/apple_calendar/save', { username, password });\n// server should invalidate cache; if not, force a refresh path that bypasses cache\nconst { data } = await api.get('/v2/calendars', { params: { ensureDefaultSelectedCalendars: true } });\nconst linked = data.connectedCalendars.some(c => c.integration?.type === 'apple_calendar');\nif (!linked) throw new Error('Apple calendar did not link — see save logs');","typeGuard":"function hasConnectedApple(list) {\n  return Array.isArray(list) && list.some(\n    (c) => c?.integration?.type === 'apple_calendar'\n  );\n}","tryCatchPattern":"try {\n  await api.get('/v2/calendars/apple_calendar/check');\n} catch (e) {\n  if (e.response?.status === 401 && /not connected/i.test(e.response?.data?.message)) {\n    // likely stale cache or incomplete link — re-save then retry once\n    await api.post('/v2/calendars/apple_calendar/save', { username, password });\n    return api.get('/v2/calendars/apple_calendar/check');\n  }\n  throw e;\n}","preventionTips":["Invalidate CalendarsCacheService after every save (deleteConnectedAndDestinationCalendarsCache).","Confirm a SelectedCalendars row is created during save before reporting success.","Avoid polling 'check' in a tight loop immediately after save — add a short delay or cache-bust."],"tags":["apple-calendar","connection","cache","unauthorized","http-401"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}