calcom/cal.diy · error · BadRequestException

Multiple SelecteCalendars found. Skipping deletion

Error message

Multiple SelecteCalendars found. Skipping deletion

What it means

BadRequestException (HTTP 400) thrown when the repository reports MULTIPLE_SELECTED_CALENDARS_FOUND during deletion. removeUserSelectedCalendar expects to remove exactly one row; finding two or more rows for the same (userId, integration, externalId) is an inconsistent state, so the service refuses to delete ambiguously rather than guessing which row.

Source

Thrown at apps/api/v2/src/modules/selected-calendars/services/selected-calendars.service.ts:60

  ) {
    const { integration, externalId, credentialId } = selectedCalendar;
    await this.calendarsService.checkCalendarCredentials(Number(credentialId), user.id);

    try {
      const removedCalendarEntry = await this.selectedCalendarsRepository.removeUserSelectedCalendar(
        user.id,
        integration,
        externalId,
        undefined
      );
      await this.calendarsCacheService.deleteConnectedAndDestinationCalendarsCache(user.id);
      return removedCalendarEntry;
    } catch (error) {
      if (error instanceof Error) {
        if (error.message === NO_SELECTED_CALENDAR_FOUND) {
          throw new NotFoundException(NO_SELECTED_CALENDAR_FOUND);
        } else if (error.message === MULTIPLE_SELECTED_CALENDARS_FOUND) {
          throw new BadRequestException(MULTIPLE_SELECTED_CALENDARS_FOUND);
        } else {
          throw new InternalServerErrorException(error.message);
        }
      }
      throw new InternalServerErrorException(
        "An unexpected error occurred while deleting the selected calendar"
      );
    }
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Investigate and deduplicate the rows in the database (keep one, delete the rest) so deletion is unambiguous.
  2. Add a unique constraint on (userId, integration, externalId) if missing, to prevent recurrence.
  3. Temporarily delete by id or with a bulk delete to clear the duplicates, then retry the normal endpoint.

Example fix

// before — normal delete fails on duplicates
await api.delete(`/selected-calendars/${integration}/${externalId}`); // 400
// after — dedupe via admin/DB, then retry
// SQL: DELETE FROM "SelectedCalendar" WHERE id IN (SELECT id FROM ... GROUP BY ... HAVING COUNT(*)>1);
await api.delete(`/selected-calendars/${integration}/${externalId}`);
Defensive patterns

Strategy: validation

Validate before calling

async function assertSingleCalendarRow(userId: number, integration: string, externalId: string) {
  const rows = await api.get('/selected-calendars', { params: { userId, integration, externalId } });
  if (rows.length > 1) throw new Error(`Duplicate rows (${rows.length}); dedupe before deleting`);
}

Type guard

const hasUniqueCalendars = (rows: { integration: string; externalId: string }[]): boolean => {
  const seen = new Set<string>();
  return rows.every(r => { const k = `${r.integration}:${r.externalId}`; if (seen.has(k)) return false; seen.add(k); return true; });
};

Try / catch

try { await api.delete(`/selected-calendars/${integration}/${externalId}`); }
catch (e) { if (/multiple selectedcalendars/i.test(e.message)) { /* run dedupe, then retry */ } else throw e; }

Prevention

When it happens

Trigger: DELETE /v2/selected-calendars when the database contains duplicate SelectedCalendar rows for the same user/integration/externalId — typically from a past bug, a missing unique constraint, or a non-atomic upsert that inserted twice.

Common situations: Legacy data created before a unique constraint was added; concurrent connect flows racing to insert the same calendar; a manual DB import that duplicated rows.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/cdb7122d4d06fe72. Report an issue: GitHub.