calcom/cal.diy · error · BadRequestException

Bookings can only be marked as absent after their scheduled

Error message

Bookings can only be marked as absent after their scheduled start time. Current time in UTC+0: ${nowUtc.toISO()}, Booking start time in UTC+0: ${bookingStartTimeUtc.toISO()}

What it means

Thrown by markAbsent when the current UTC time is before the booking's start time. A booking can only be marked absent (no-show) after its scheduled start has passed. BadRequestException (HTTP 400) includes both timestamps for debugging.

Source

Thrown at apps/api/v2/src/platform/bookings/2024-08-13/services/bookings.service.ts:963

  async markAbsent(
    bookingUid: string,
    bookingOwnerId: number,
    body: MarkAbsentBookingInput_2024_08_13,
    userUuid: string
  ) {
    const bodyTransformed = this.inputService.transformInputMarkAbsentBooking(body);
    const bookingBefore = await this.bookingsRepository.getByUid(bookingUid);

    if (!bookingBefore) {
      throw new NotFoundException(`Booking with uid=${bookingUid} not found.`);
    }

    const nowUtc = DateTime.utc();
    const bookingStartTimeUtc = DateTime.fromJSDate(bookingBefore.startTime, { zone: "utc" });

    if (nowUtc < bookingStartTimeUtc) {
      throw new BadRequestException(
        `Bookings can only be marked as absent after their scheduled start time. Current time in UTC+0: ${nowUtc.toISO()}, Booking start time in UTC+0: ${bookingStartTimeUtc.toISO()}`
      );
    }

    const platformClientParams = bookingBefore?.eventTypeId
      ? await this.platformBookingsService.getOAuthClientParams(bookingBefore.eventTypeId)
      : undefined;

    await handleMarkNoShow({
      bookingUid,
      attendees: bodyTransformed.attendees,
      noShowHost: bodyTransformed.noShowHost,
      userId: bookingOwnerId,
      platformClientParams,
    });

    const booking = await this.bookingsRepository.getByUidWithAttendeesAndUserAndEvent(bookingUid);

View on GitHub (pinned to 176037d0af)

Solutions

  1. Schedule the markAbsent call for after the booking end time (e.g. via a delayed job).
  2. Compare local time to booking.startTime before calling and skip if in the future.
  3. Trigger from a post-event webhook rather than a creation webhook.

Example fix

// before
await client.post(`/v2/bookings/${uid}/mark-absent`, body);
// after
const b = await client.get(`/v2/bookings/${uid}`);
if (new Date() > new Date(b.startTime)) {
  await client.post(`/v2/bookings/${uid}/mark-absent`, body);
}
Defensive patterns

Strategy: validation

Validate before calling

const b = await client.get(`/v2/bookings/${uid}`);
if (new Date().getTime() < new Date(b.startTime).getTime()) throw new Error('Cannot mark absent before start time');

Type guard

function isPastStart(startTime: string): boolean {
  return Date.now() >= new Date(startTime).getTime();
}

Try / catch

try { await client.post(`/v2/bookings/${uid}/mark-absent`, body); }
catch (e) {
  if (e.status === 400 && /after their scheduled start time/i.test(e.message)) { /* reschedule for later */ }
  else throw e;
}

Prevention

When it happens

Trigger: Calling markAbsent on a future booking — e.g. immediately after creation or before the meeting time. The guard compares DateTime.utc() < bookingStartTimeUtc.

Common situations: Automated no-show processing triggered too early; timezone confusion causing premature calls; webhook firing on booking creation instead of completion.

Related errors


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