Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/pages/api/cron/bookingReminder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});

const attendeesList = await Promise.all(attendeesListPromises);

const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar;
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For collective events with multiple hosts, this logic only considers the primary destination calendar (booking.destinationCalendar). It fails to load all calendar events from the booking's references, leading to incomplete data for reminders. The CalendarEvent object is constructed with only a single host's calendar, which is incorrect for multi-host bookings.

    DEV MODE: This violation would have been filtered out by GPT-5.

Reasoning:
GPT-5: Insufficient in-file evidence to confirm the multi-host calendar requirement. The claim depends on architecture outside the visible context; to minimize false positives, filter this.

Prompt for AI agents
Address the following comment on apps/web/pages/api/cron/bookingReminder.ts at line 107:

<comment>For collective events with multiple hosts, this logic only considers the primary destination calendar (`booking.destinationCalendar`). It fails to load all calendar events from the booking&#39;s references, leading to incomplete data for reminders. The `CalendarEvent` object is constructed with only a single host&#39;s calendar, which is incorrect for multi-host bookings.

        DEV MODE: This violation would have been filtered out by GPT-5.
Reasoning:
• **GPT-5**: Insufficient in-file evidence to confirm the multi-host calendar requirement. The claim depends on architecture outside the visible context; to minimize false positives, filter this.</comment>

<file context>
@@ -104,7 +104,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
 
       const attendeesList = await Promise.all(attendeesListPromises);
-
+      const selectedDestinationCalendar = booking.destinationCalendar || user.destinationCalendar;
       const evt: CalendarEvent = {
         type: booking.title,
</file context>

[internal] Confidence score: 9/10

[internal] Posted by: System Design Agent

Fix with Cubic

const evt: CalendarEvent = {
type: booking.title,
title: booking.title,
Expand All @@ -127,7 +127,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
attendees: attendeesList,
uid: booking.uid,
recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
destinationCalendar: booking.destinationCalendar || user.destinationCalendar,
destinationCalendar: selectedDestinationCalendar ? [selectedDestinationCalendar] : [],
};

await sendOrganizerRequestReminderEmail(evt);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/playwright/webhook.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ test.describe("BOOKING_REJECTED", async () => {
},
],
location: "[redacted/dynamic]",
destinationCalendar: null,
destinationCalendar: [],
// hideCalendarNotes: false,
requiresConfirmation: "[redacted/dynamic]",
eventTypeId: "[redacted/dynamic]",
Expand Down
33 changes: 22 additions & 11 deletions packages/app-store/googlecalendar/lib/CalendarService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default class GoogleCalendarService implements Calendar {
};
};

async createEvent(calEventRaw: CalendarEvent): Promise<NewCalendarEventType> {
async createEvent(calEventRaw: CalendarEvent, credentialId: number): Promise<NewCalendarEventType> {
const eventAttendees = calEventRaw.attendees.map(({ id: _id, ...rest }) => ({
...rest,
responseStatus: "accepted",
Expand All @@ -97,6 +97,10 @@ export default class GoogleCalendarService implements Calendar {
responseStatus: "accepted",
})) || [];
return new Promise(async (resolve, reject) => {
const [mainHostDestinationCalendar] =
calEventRaw?.destinationCalendar && calEventRaw?.destinationCalendar.length > 0
? calEventRaw.destinationCalendar
: [];
const myGoogleAuth = await this.auth.getToken();
const payload: calendar_v3.Schema$Event = {
summary: calEventRaw.title,
Expand All @@ -115,8 +119,8 @@ export default class GoogleCalendarService implements Calendar {
id: String(calEventRaw.organizer.id),
responseStatus: "accepted",
organizer: true,
email: calEventRaw.destinationCalendar?.externalId
? calEventRaw.destinationCalendar.externalId
email: mainHostDestinationCalendar?.externalId
? mainHostDestinationCalendar.externalId
: calEventRaw.organizer.email,
},
...eventAttendees,
Expand All @@ -138,13 +142,16 @@ export default class GoogleCalendarService implements Calendar {
const calendar = google.calendar({
version: "v3",
});
const selectedCalendar = calEventRaw.destinationCalendar?.externalId
? calEventRaw.destinationCalendar.externalId
: "primary";
// Find in calEventRaw.destinationCalendar the one with the same credentialId

const selectedCalendar = calEventRaw.destinationCalendar?.find(
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no destination calendar matches this credential, selectedCalendar stays undefined; while the insert falls back to "primary", the subsequent patch reuses the undefined value and Google rejects the request because calendarId is required. Please default selectedCalendar to a valid ID before it is reused.

Prompt for AI agents
Address the following comment on packages/app-store/googlecalendar/lib/CalendarService.ts at line 147:

<comment>If no destination calendar matches this credential, `selectedCalendar` stays undefined; while the insert falls back to &quot;primary&quot;, the subsequent patch reuses the undefined value and Google rejects the request because `calendarId` is required. Please default `selectedCalendar` to a valid ID before it is reused.</comment>

<file context>
@@ -138,13 +142,16 @@ export default class GoogleCalendarService implements Calendar {
-        : &quot;primary&quot;;
+      // Find in calEventRaw.destinationCalendar the one with the same credentialId
+
+      const selectedCalendar = calEventRaw.destinationCalendar?.find(
+        (cal) =&gt; cal.credentialId === credentialId
+      )?.externalId;
</file context>

[internal] Confidence score: 8/10

[internal] Posted by: General AI Review Agent

Suggested change
const selectedCalendar = calEventRaw.destinationCalendar?.find(
const selectedCalendar = calEventRaw.destinationCalendar?.find((cal) => cal.credentialId === credentialId)?.externalId ?? "primary";
Fix with Cubic

(cal) => cal.credentialId === credentialId
)?.externalId;

calendar.events.insert(
{
auth: myGoogleAuth,
calendarId: selectedCalendar,
calendarId: selectedCalendar || "primary",
requestBody: payload,
conferenceDataVersion: 1,
sendUpdates: "none",
Expand Down Expand Up @@ -188,6 +195,8 @@ export default class GoogleCalendarService implements Calendar {

async updateEvent(uid: string, event: CalendarEvent, externalCalendarId: string): Promise<any> {
return new Promise(async (resolve, reject) => {
const [mainHostDestinationCalendar] =
event?.destinationCalendar && event?.destinationCalendar.length > 0 ? event.destinationCalendar : [];
const myGoogleAuth = await this.auth.getToken();
const eventAttendees = event.attendees.map(({ ...rest }) => ({
...rest,
Expand Down Expand Up @@ -216,8 +225,8 @@ export default class GoogleCalendarService implements Calendar {
id: String(event.organizer.id),
organizer: true,
responseStatus: "accepted",
email: event.destinationCalendar?.externalId
? event.destinationCalendar.externalId
email: mainHostDestinationCalendar?.externalId
? mainHostDestinationCalendar.externalId
: event.organizer.email,
},
...(eventAttendees as any),
Expand All @@ -244,7 +253,7 @@ export default class GoogleCalendarService implements Calendar {

const selectedCalendar = externalCalendarId
? externalCalendarId
: event.destinationCalendar?.externalId;
: event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId;
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When externalCalendarId is null, this fallback never finds a match, so calendarId becomes undefined and the subsequent events.update call fails. Please restore a real fallback (e.g., the host’s calendar) instead of matching against the null input.

Prompt for AI agents
Address the following comment on packages/app-store/googlecalendar/lib/CalendarService.ts at line 256:

<comment>When `externalCalendarId` is null, this fallback never finds a match, so `calendarId` becomes undefined and the subsequent `events.update` call fails. Please restore a real fallback (e.g., the host’s calendar) instead of matching against the null input.</comment>

<file context>
@@ -244,7 +253,7 @@ export default class GoogleCalendarService implements Calendar {
       const selectedCalendar = externalCalendarId
         ? externalCalendarId
-        : event.destinationCalendar?.externalId;
+        : event.destinationCalendar?.find((cal) =&gt; cal.externalId === externalCalendarId)?.externalId;
 
       calendar.events.update(
</file context>

[internal] Confidence score: 9/10

[internal] Posted by: General AI Review Agent

Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When externalCalendarId is not passed we now end up with selectedCalendar undefined, so the Google update call is made without a calendarId and fails. Restore a real fallback (e.g. the first destination calendar) instead of searching for externalCalendarId again.

Prompt for AI agents
Address the following comment on packages/app-store/googlecalendar/lib/CalendarService.ts at line 256:

<comment>When `externalCalendarId` is not passed we now end up with `selectedCalendar` undefined, so the Google update call is made without a calendarId and fails. Restore a real fallback (e.g. the first destination calendar) instead of searching for `externalCalendarId` again.</comment>

<file context>
@@ -244,7 +253,7 @@ export default class GoogleCalendarService implements Calendar {
       const selectedCalendar = externalCalendarId
         ? externalCalendarId
-        : event.destinationCalendar?.externalId;
+        : event.destinationCalendar?.find((cal) =&gt; cal.externalId === externalCalendarId)?.externalId;
 
       calendar.events.update(
</file context>

[internal] Confidence score: 9/10

[internal] Posted by: General AI Review Agent

Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateEvent may pass undefined calendarId; createEvent follow-up patch may pass undefined calendarId; organizer attendee email set to calendarId; and patch errors are swallowed

Prompt for AI agents
Address the following comment on packages/app-store/googlecalendar/lib/CalendarService.ts at line 256:

<comment>updateEvent may pass undefined calendarId; createEvent follow-up patch may pass undefined calendarId; organizer attendee email set to calendarId; and patch errors are swallowed</comment>

<file context>
@@ -244,7 +253,7 @@ export default class GoogleCalendarService implements Calendar {
       const selectedCalendar = externalCalendarId
         ? externalCalendarId
-        : event.destinationCalendar?.externalId;
+        : event.destinationCalendar?.find((cal) =&gt; cal.externalId === externalCalendarId)?.externalId;
 
       calendar.events.update(
</file context>

[internal] Confidence score: 9/10

[internal] Posted by: Functional Bugs Agent

Fix with Cubic


calendar.events.update(
{
Expand Down Expand Up @@ -303,7 +312,9 @@ export default class GoogleCalendarService implements Calendar {
});

const defaultCalendarId = "primary";
const calendarId = externalCalendarId ? externalCalendarId : event.destinationCalendar?.externalId;
const calendarId = externalCalendarId
? externalCalendarId
: event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId;

calendar.events.delete(
{
Expand Down
12 changes: 8 additions & 4 deletions packages/app-store/larkcalendar/lib/CalendarService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ export default class LarkCalendarService implements Calendar {
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
let eventId = "";
let eventRespData;
const calendarId = event.destinationCalendar?.externalId;
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const calendarId = mainHostDestinationCalendar?.externalId;
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calendar services for Lark, Office365, and iCal have not been correctly updated to handle multiple destination calendars. They consistently only process the first calendar in the destinationCalendar array, which will cause the feature to fail for any users with these integrations. Furthermore, their createEvent method signatures are not updated to match the Calendar interface, which now requires a credentialId.

DEV MODE: This violation would have been filtered out by screening filters. Failing filters: typescriptUnderstanding.

Prompt for AI agents
Address the following comment on packages/app-store/larkcalendar/lib/CalendarService.ts at line 129:

<comment>The calendar services for Lark, Office365, and iCal have not been correctly updated to handle multiple destination calendars. They consistently only process the first calendar in the `destinationCalendar` array, which will cause the feature to fail for any users with these integrations. Furthermore, their `createEvent` method signatures are not updated to match the `Calendar` interface, which now requires a `credentialId`.

*DEV MODE: This violation would have been filtered out by screening filters. Failing filters: typescriptUnderstanding.*</comment>

<file context>
@@ -125,7 +125,8 @@ export default class LarkCalendarService implements Calendar {
     let eventRespData;
-    const calendarId = event.destinationCalendar?.externalId;
+    const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
+    const calendarId = mainHostDestinationCalendar?.externalId;
     if (!calendarId) {
       throw new Error(&quot;no calendar id&quot;);
</file context>

[internal] Confidence score: 10/10

[internal] Posted by: System Design Agent

Fix with Cubic

if (!calendarId) {
throw new Error("no calendar id");
}
Expand Down Expand Up @@ -160,7 +161,8 @@ export default class LarkCalendarService implements Calendar {
}

private createAttendees = async (event: CalendarEvent, eventId: string) => {
const calendarId = event.destinationCalendar?.externalId;
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const calendarId = mainHostDestinationCalendar?.externalId;
if (!calendarId) {
this.log.error("no calendar id provided in createAttendees");
throw new Error("no calendar id provided in createAttendees");
Expand All @@ -187,7 +189,8 @@ export default class LarkCalendarService implements Calendar {
async updateEvent(uid: string, event: CalendarEvent, externalCalendarId?: string) {
const eventId = uid;
let eventRespData;
const calendarId = externalCalendarId || event.destinationCalendar?.externalId;
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const calendarId = externalCalendarId || mainHostDestinationCalendar?.externalId;
if (!calendarId) {
this.log.error("no calendar id provided in updateEvent");
throw new Error("no calendar id provided in updateEvent");
Expand Down Expand Up @@ -231,7 +234,8 @@ export default class LarkCalendarService implements Calendar {
* @returns
*/
async deleteEvent(uid: string, event: CalendarEvent, externalCalendarId?: string) {
const calendarId = externalCalendarId || event.destinationCalendar?.externalId;
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const calendarId = externalCalendarId || mainHostDestinationCalendar?.externalId;
if (!calendarId) {
this.log.error("no calendar id provided in deleteEvent");
throw new Error("no calendar id provided in deleteEvent");
Expand Down
5 changes: 3 additions & 2 deletions packages/app-store/office365calendar/lib/CalendarService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ export default class Office365CalendarService implements Calendar {
}

async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new destructuring assumes event.destinationCalendar is always an array; when it is still a single object (common for older bookings), this throws at runtime because the object is not iterable. Please keep compatibility with object values.

Prompt for AI agents
Address the following comment on packages/app-store/office365calendar/lib/CalendarService.ts at line 73:

<comment>The new destructuring assumes `event.destinationCalendar` is always an array; when it is still a single object (common for older bookings), this throws at runtime because the object is not iterable. Please keep compatibility with object values.</comment>

<file context>
@@ -70,9 +70,10 @@ export default class Office365CalendarService implements Calendar {
   }
 
   async createEvent(event: CalendarEvent): Promise&lt;NewCalendarEventType&gt; {
+    const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
     try {
-      const eventsUrl = event.destinationCalendar?.externalId
</file context>

[internal] Confidence score: 7/10

[internal] Posted by: General AI Review Agent

Suggested change
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const mainHostDestinationCalendar = Array.isArray(event.destinationCalendar)
? event.destinationCalendar[0]
: event.destinationCalendar ?? undefined;
Fix with Cubic

try {
const eventsUrl = event.destinationCalendar?.externalId
? `/me/calendars/${event.destinationCalendar?.externalId}/events`
const eventsUrl = mainHostDestinationCalendar?.externalId
? `/me/calendars/${mainHostDestinationCalendar?.externalId}/events`
: "/me/calendar/events";

const response = await this.fetcher(eventsUrl, {
Expand Down
43 changes: 24 additions & 19 deletions packages/core/CalendarManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ export const getBusyCalendarTimes = async (

export const createEvent = async (
credential: CredentialWithAppName,
calEvent: CalendarEvent
calEvent: CalendarEvent,
externalId?: string
): Promise<EventResult<NewCalendarEventType>> => {
const uid: string = getUid(calEvent);
const calendar = await getCalendar(credential);
Expand All @@ -226,29 +227,31 @@ export const createEvent = async (

// Check if the disabledNotes flag is set to true
if (calEvent.hideCalendarNotes) {
calEvent.additionalNotes = "Notes have been hidden by the organiser"; // TODO: i18n this string?
calEvent.additionalNotes = "Notes have been hidden by the organizer"; // TODO: i18n this string?
}

// TODO: Surface success/error messages coming from apps to improve end user visibility
const creationResult = calendar
? await calendar.createEvent(calEvent).catch(async (error: { code: number; calError: string }) => {
success = false;
/**
* There is a time when selectedCalendar externalId doesn't match witch certain credential
* so google returns 404.
* */
if (error?.code === 404) {
? await calendar
.createEvent(calEvent, credential.id)
.catch(async (error: { code: number; calError: string }) => {
success = false;
/**
* There is a time when selectedCalendar externalId doesn't match witch certain credential
* so google returns 404.
* */
if (error?.code === 404) {
return undefined;
}
if (error?.calError) {
calError = error.calError;
}
log.error("createEvent failed", JSON.stringify(error), calEvent);
// @TODO: This code will be off till we can investigate an error with it
//https://github.com/calcom/cal.com/issues/3949
// await sendBrokenIntegrationEmail(calEvent, "calendar");
return undefined;
}
if (error?.calError) {
calError = error.calError;
}
log.error("createEvent failed", JSON.stringify(error), calEvent);
// @TODO: This code will be off till we can investigate an error with it
//https://github.com/calcom/cal.com/issues/3949
// await sendBrokenIntegrationEmail(calEvent, "calendar");
return undefined;
})
})
: undefined;

return {
Expand All @@ -261,6 +264,8 @@ export const createEvent = async (
originalEvent: calEvent,
calError,
calWarnings: creationResult?.additionalInfo?.calWarnings || [],
externalId,
credentialId: credential.id,
};
};

Expand Down
Loading