calcom/cal.diy · error · BadRequestException

Listed cals and URLs mismatch: ${listedCals.length} vs. ${ur

Error message

Listed cals and URLs mismatch: ${listedCals.length} vs. ${urls.length}

What it means

Raised inside IcsFeedService.save after BuildIcsFeedCalendarService(...).listCalendars() returns. The service is given N ICS feed URLs (urls.length) and expects the DAV/ICS adapter to enumerate exactly that many calendars; a count mismatch means one or more feeds could not be resolved (unreachable, unparseable, or merged), so the whole credential-upsert is aborted with BadRequestException. The error message echoes the two counts so you can see which side is short.

Source

Thrown at apps/api/v2/src/platform/calendars/services/ics-feed.service.ts:56

      userId: userId,
      teamId: null,
      appId: ICS_CALENDAR,
      invalid: false,
      delegationCredentialId: null,
      encryptedKey: null,
    };

    try {
      const dav = BuildIcsFeedCalendarService({
        id: 0,
        ...data,
        user: { email: userEmail },
      });

      const listedCals = await dav.listCalendars();

      if (listedCals.length !== urls.length) {
        throw new BadRequestException(
          `Listed cals and URLs mismatch: ${listedCals.length} vs. ${urls.length}`
        );
      }

      const credential = await this.credentialRepository.upsertUserAppCredential(
        ICS_CALENDAR_TYPE,
        data.key,
        userId
      );

      await this.calendarsCacheService.deleteConnectedAndDestinationCalendarsCache(userId);

      return {
        status: SUCCESS_STATUS,
        data: {
          id: credential.id,
          type: credential.type,
          userId: credential.userId,

View on GitHub (pinned to 176037d0af)

Solutions

  1. Fetch each URL in urls[] with curl -I and confirm a 200 with content-type text/calendar before calling /v2/calendars/ics/save.
  2. Use the 'private'/'secret address in iCal format' link for every entry (Google: calendar Settings -> Integrate calendar -> Secret address).
  3. Reduce urls[] to a single URL to isolate which feed is shorting the count, then add the rest back one by one.
  4. If all feeds are valid but still mismatch, retry once; a transient network error during listCalendars can shrink listedCals without throwing.

Example fix

// before
await api.post('/v2/calendars/ics/save', { urls: [gcalPublicPageUrl, privateIcsUrl] });

// after
await api.post('/v2/calendars/ics/save', { urls: [privateIcsUrl] });
Defensive patterns

Strategy: validation

Validate before calling

async function preflightIcsUrls(urls: string[]) {
  for (const u of urls) {
    const res = await fetch(u, { method: 'GET' });
    const ct = res.headers.get('content-type') || '';
    if (!res.ok || !/text\/calendar|ics/.test(ct)) {
      throw new Error(`Bad ICS feed ${u}: status=${res.status} content-type=${ct}`);
    }
  }
}
await preflightIcsUrls(urls);

Type guard

function isIcsUrlList(urls: unknown): urls is string[] {
  return Array.isArray(urls) && urls.length > 0 && urls.every(u => typeof u === 'string' && /^https?:\/\//.test(u));
}

Try / catch

try {
  await icsFeedService.save(userId, userEmail, urls, readonly);
} catch (e) {
  if (e instanceof BadRequestException && e.message.startsWith('Listed cals and URLs mismatch')) {
    // isolate the failing URL by retrying one at a time
  } else throw e;
}

Prevention

When it happens

Trigger: POSTing an array of ICS URLs where at least one URL 404s, returns non-ICS content, requires auth that wasn't supplied, or is a duplicate that the DAV library collapses into one entry; a URL that is reachable but contains zero VEVENT calendars; a network/timeout during listCalendars that returns a partial list without throwing.

Common situations: Public Google/Outlook ICS links that require an authenticated session, or that rate-limit the server IP; feed URLs behind a CDN that serve an HTML login page instead of .ics; copy-paste of the calendar's HTML page URL instead of the 'secret address in iCal format'; feed temporarily down at save time.

Related errors


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