calcom/cal.diy · error · InternalServerErrorException

Could not create recurring booking.

Error message

Could not create recurring booking.

What it means

Thrown as a 500 InternalServerError after the recurring-booking try/catch's handleBookingErrors call. handleBookingErrors is typed to return void (not 'never'), so TypeScript control-flow analysis sees the possibility of falling through past the catch block. This throw is the compiler-required safety net; in practice handleBookingErrors always throws, so this message indicates either an unhandled error shape or a logic bug in handleBookingErrors itself.

Source

Thrown at apps/api/v2/src/platform/bookings/2024-04-15/controllers/bookings.controller.ts:352

          hostname: bookingRequest.headers?.host || "",
          platformClientId: bookingRequest.platformClientId,
          platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
          platformCancelUrl: bookingRequest.platformCancelUrl,
          platformBookingUrl: bookingRequest.platformBookingUrl,
          platformBookingLocation: bookingRequest.platformBookingLocation,
          noEmail: bookingRequest.body.noEmail,
        },
        creationSource: "API_V2",
      });

      return {
        status: SUCCESS_STATUS,
        data: createdBookings,
      };
    } catch (err) {
      this.handleBookingErrors(err, "recurring");
    }
    throw new InternalServerErrorException("Could not create recurring booking.");
  }

  private async getOwner(req: Request): Promise<{ id: number; uuid: string } | null> {
    try {
      const bearerToken = req.get("Authorization")?.replace("Bearer ", "");
      if (!bearerToken) {
        return null;
      }

      let ownerId: number | null = null;

      if (isApiKey(bearerToken, this.config.get<string>("api.apiKeyPrefix") ?? "cal_")) {
        const strippedApiKey = stripApiKey(bearerToken, this.config.get<string>("api.keyPrefix"));
        const apiKeyHash = sha256Hash(strippedApiKey);
        const keyData = await this.apiKeyRepository.getApiKeyFromHash(apiKeyHash);
        ownerId = keyData?.userId ?? null;
      } else {
        // Access Token

View on GitHub (pinned to 176037d0af)

Solutions

  1. Inspect the server logs for the original error that handleBookingErrors swallowed — the logger.error calls in dependent services capture the root cause.
  2. Verify the thrown value is a proper Error instance. If a service throws a plain object or string, wrap it: throw new Error(...).
  3. If you control the recurring booking service, ensure all catch paths throw a typed Error, HttpError, or HttpException so handleBookingErrors can route correctly.
  4. Consider changing handleBookingErrors return type to 'never' so TypeScript guarantees it always throws, eliminating this dead code path.

Example fix

// before
private handleBookingErrors(err: Error | HttpError | unknown, type?: ...): void {
  // ...
  throw new InternalServerErrorException(errMsg);
}

// after — tell TypeScript this always throws
private handleBookingErrors(err: Error | HttpError | unknown, type?: ...): never {
  // ...
  throw new InternalServerErrorException(errMsg);
}
Defensive patterns

Strategy: try-catch

Try / catch

// Client-side: catch 500 and inspect the body for context
try {
  const res = await fetch('/v2/bookings/recurring', { method: 'POST', body: JSON.stringify(payload) });
  if (!res.ok) {
    const body = await res.json();
    // body.message may contain the underlying error if handleBookingErrors forwarded it
    throw new Error(body.message || 'Recurring booking failed');
  }
} catch (err) {
  // Retry with corrected payload or surface to user
  console.error('Recurring booking creation failed:', err);
}

Prevention

When it happens

Trigger: POST /v2/bookings/recurring where the recurringBookingService.createBooking call throws an error that handleBookingErrors processes. The message surfaces only if handleBookingErrors returns without throwing (e.g., the error object is of an unexpected type that doesn't match HttpError or Error instanceof checks, or a future refactor breaks the always-throw contract).

Common situations: A non-Error value is thrown inside the recurring booking service (e.g., a bare string, a rejected promise with a non-Error rejection reason, or a Prisma internal error that doesn't extend Error). Also seen after refactoring handleBookingErrors where a new code path returns instead of throws.

Related errors


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