-
-
Notifications
You must be signed in to change notification settings - Fork 187
Fix Google Calendar Sequence Conflicts #340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Preserve sequence numbers when updating Google Calendar events to prevent "Invalid sequence value" errors - Add retry logic to re-fetch events and use current sequence numbers when conflicts occur - Apply fixes to updateEvent, responseToEvent, acceptEvent methods in GoogleCalendarProvider - Add sequence conflict handling to GoogleMeetProvider createConference method - Ensure all Google Calendar API update operations include proper sequence number handling Fixes #59095795 - TRPCError: 400 Invalid sequence value error
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Requesting review from @JeanMeijer who has experience with the following files modified in this PR:
|
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughImplements optimistic concurrency for Google Calendar and Google Meet by preserving and retrying on sequence conflicts; adjusts UUID generation to use globalThis.crypto/randomUUID in web utils and node:crypto in API routes; and removes an unused import from auth server. No public APIs changed. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Join our Discord community for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Tembo is working on fixing this failing workflow: |
|
Tembo is working on fixing this failing workflow: |
- Add proper import for randomUUID from node:crypto in API packages - Use globalThis.crypto.randomUUID in web app for browser compatibility - Fix TypeScript compilation errors in workflow Resolves type checking failures in CI workflow.
|
Tembo is working on fixing this failing workflow: |
|
Tembo is working on fixing this failing workflow: |
|
Tembo is working on fixing this failing workflow: |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (5)
packages/api/src/providers/conferencing/google-meet.ts (2)
86-141: Strengthen sequence-conflict detectionCurrently you rely on error.message.includes("Invalid sequence value"). If @repo/google-calendar exposes structured errors (status, reason), prefer a predicate based on code/reason to avoid brittle string matching. If not available, at least use a case-insensitive regex.
I can factor a small helper like:
function isInvalidSequenceError(err: unknown): boolean { return err instanceof Error && /invalid sequence value/i.test(err.message); }and use it in both try/catch blocks to keep logic consistent.
34-37: Nit: operation nameMinor: withErrorHandler("createConferencing", ...) doesn’t match the method name createConference. Consider aligning for clearer logs.
packages/api/src/providers/calendars/google-calendar.ts (3)
372-406: Use freshEvent payload on retry for clarityYou already reassign event with the fresh fetch. For readability and to avoid accidental drift, consider destructuring to freshEvent and using it explicitly in the retry update call.
- event = await this.client.calendars.events.retrieve(eventId, { + const freshEvent = await this.client.calendars.events.retrieve(eventId, { calendarId, }); - const freshAttendees = event.attendees ?? []; + const freshAttendees = freshEvent.attendees ?? []; @@ - await this.client.calendars.events.update(eventId, { - ...event, + await this.client.calendars.events.update(eventId, { + ...freshEvent, calendarId, attendees: freshAttendees, // Use the fresh sequence number - sequence: event.sequence, + sequence: freshEvent.sequence, sendUpdates: "all", });
448-484: Respond retry path: use freshly retrieved event object explicitlySame readability nit as acceptEvent; prefer using a freshEvent variable explicitly.
- event = await this.client.calendars.events.retrieve(eventId, { + const freshEvent = await this.client.calendars.events.retrieve(eventId, { calendarId, }); - if (!event.attendees) { + if (!freshEvent.attendees) { throw new Error("Event has no attendees"); } - const freshSelfIndex = event.attendees.findIndex( + const freshSelfIndex = freshEvent.attendees.findIndex( (attendee) => attendee.self, ); @@ - event.attendees[freshSelfIndex] = { - ...event.attendees[freshSelfIndex], + freshEvent.attendees[freshSelfIndex] = { + ...freshEvent.attendees[freshSelfIndex], responseStatus: toGoogleCalendarAttendeeResponseStatus( response.status, ), }; - await this.client.calendars.events.update(eventId, { - ...event, + await this.client.calendars.events.update(eventId, { + ...freshEvent, calendarId, // Use the fresh sequence number - sequence: event.sequence, + sequence: freshEvent.sequence, sendUpdates: response.sendUpdate ? "all" : "none", });
186-307: Centralize sequence-conflict detectionYou repeat the string check for "Invalid sequence value" across updateEvent, acceptEvent, and responseToEvent. Encapsulate in a helper for maintainability and to enable future expansion to other error shapes.
Outside these ranges, add:
function isInvalidSequenceError(err: unknown): boolean { return err instanceof Error && /invalid sequence value/i.test(err.message); }Then within the catch blocks:
- if ( - error instanceof Error && - error.message.includes("Invalid sequence value") - ) { + if (isInvalidSequenceError(error)) {Also applies to: 344-407, 420-490
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
apps/web/src/lib/utils/calendar.ts(1 hunks)packages/api/src/providers/calendars/google-calendar.ts(5 hunks)packages/api/src/providers/conferencing/google-meet.ts(2 hunks)packages/api/src/routers/events.ts(3 hunks)packages/auth/src/server.ts(0 hunks)
💤 Files with no reviewable changes (1)
- packages/auth/src/server.ts
🧰 Additional context used
🪛 Biome (2.1.2)
packages/api/src/providers/conferencing/google-meet.ts
[error] 46-47: Expected a statement but instead found '<<<<<<< HEAD'.
Expected a statement here.
(parse)
[error] 58-59: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '==='.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 59-59: Expected an expression but instead found '==='.
Expected an expression here.
(parse)
[error] 59-59: Expected an expression but instead found '='.
Expected an expression here.
(parse)
[error] 57-59: Invalid assignment to { type: "hangoutsMeet", ======
This expression cannot be assigned to
(parse)
[error] 60-60: Expected an expression, or an assignment but instead found 'const'.
Expected an expression, or an assignment here.
(parse)
[error] 60-60: expected : but instead found updatedEvent
Remove updatedEvent
(parse)
[error] 73-74: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '>'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 74-74: Expected an expression but instead found '>>>'.
Expected an expression here.
(parse)
[error] 74-74: Expected an expression but instead found '>'.
Expected an expression here.
(parse)
[error] 77-77: Expected an expression but instead found '}'.
Expected an expression here.
(parse)
[error] 78-78: Expected a property, a shorthand property, a getter, a setter, or a method but instead found ')'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 98-99: Expected a statement but instead found '<<<<<<< HEAD'.
Expected a statement here.
(parse)
[error] 110-111: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '==='.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 111-111: Expected an expression but instead found '==='.
Expected an expression here.
(parse)
[error] 111-111: Expected an expression but instead found '='.
Expected an expression here.
(parse)
[error] 109-111: Invalid assignment to { type: "hangoutsMeet", ======
This expression cannot be assigned to
(parse)
[error] 112-112: Expected an expression, or an assignment but instead found 'const'.
Expected an expression, or an assignment here.
(parse)
[error] 112-112: expected : but instead found updatedEvent
Remove updatedEvent
(parse)
[error] 125-126: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '>'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 126-126: Expected an expression but instead found '>>>'.
Expected an expression here.
(parse)
[error] 126-126: Expected an expression but instead found '>'.
Expected an expression here.
(parse)
[error] 129-129: Expected an expression but instead found '}'.
Expected an expression here.
(parse)
[error] 130-130: Expected a property, a shorthand property, a getter, a setter, or a method but instead found ')'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 60-60: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 112-112: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 60-60: Can't assign updatedEvent because it's a constant.
This is where the variable is defined as constant.
Unsafe fix: Replace const with let if you assign it to a new value.
(lint/correctness/noConstAssign)
[error] 112-112: Can't assign updatedEvent because it's a constant.
This is where the variable is defined as constant.
Unsafe fix: Replace const with let if you assign it to a new value.
(lint/correctness/noConstAssign)
🪛 GitHub Actions: Check formatting
packages/api/src/providers/conferencing/google-meet.ts
[error] 47-47: Merge conflict marker encountered. Resolve merge conflicts and remove conflict markers (e.g., <<<<<<< HEAD, =======, >>>>>>>) to restore valid TypeScript syntax.
[error] 47-47: bun run prettier --check failed due to parsing error (Unexpected token) at line 47:0 in google-meet.ts. This is likely caused by the merge conflict markers.
🪛 GitHub Actions: Lint
packages/api/src/providers/conferencing/google-meet.ts
[error] 47-47: ESLint: Parsing error: Merge conflict marker encountered. (Step: bun run lint in packages/api)
🔇 Additional comments (8)
apps/web/src/lib/utils/calendar.ts (1)
31-31: Calendar util is used only in Client Components—no SSR import paths foundI verified that every import of
createEventId(and its callercreateDraftEvent) lives in modules explicitly marked with"use client"(e.g.event-form.tsx,month-view.tsx,week-view.tsx,event-hotkeys.tsx), and I found no imports in any server-rendered pages or API routes. Since this util is only ever invoked in browser contexts,globalThis.crypto.randomUUID()will always be defined in supported modern browsers.Because there are no SSR or non-secure contexts hitting this code, adding a guard or fallback for
crypto.randomUUIDisn’t necessary. Likewise, TypeScript will infer the return type asstring, so an explicit annotation is optional.Likely an incorrect or invalid review comment.
packages/api/src/routers/events.ts (3)
1-1: LGTM: switch to node:crypto randomUUID in API layerImporting randomUUID from node:crypto is the right choice in the server runtime. This avoids depending on the Web Crypto global in Node.
278-286: Consistent server-side ID generationUsing randomUUID() when cloning to a different Google account is correct. It keeps the API layer consistent with Node’s crypto source for UUIDs.
391-399: Same here — goodThe second usage of randomUUID() for cross-account move is also correct. No issues.
packages/api/src/providers/conferencing/google-meet.ts (1)
80-86: Good: verify conference data before parsingThe explicit check for updatedEvent.conferenceData prior to parsing is solid and avoids unsafe non-null assertions elsewhere.
packages/api/src/providers/calendars/google-calendar.ts (3)
192-204: Good start: fetch current event and preserve sequenceFetching existingEvent then setting sequence: existingEvent.sequence in eventToUpdate correctly addresses Google’s optimistic concurrency.
344-371: Accept flow: preserves sequence and retries — goodThe acceptEvent branch correctly builds attendees, preserves sequence, and retries once on sequence conflicts.
420-447: Respond flow: preserves sequence and sendUpdates — goodThe first attempt properly updates the self attendee, preserves sequence, and respects sendUpdates based on the input.
| // Handle response status update within the same call for Google Calendar | ||
| if (event.response && event.response.status !== "unknown") { | ||
| if (!existingEvent.attendees) { | ||
| throw new Error("Event has no attendees"); | ||
| } | ||
|
|
||
| const selfIndex = existingEvent.attendees.findIndex( | ||
| (attendee) => attendee.self, | ||
| ); | ||
|
|
||
| if (selfIndex === -1) { | ||
| throw new Error("User is not an attendee"); | ||
| } | ||
|
|
||
| const updatedAttendees = [...existingEvent.attendees]; | ||
| updatedAttendees[selfIndex] = { | ||
| ...updatedAttendees[selfIndex], | ||
| responseStatus: toGoogleCalendarAttendeeResponseStatus( | ||
| event.response.status, | ||
| ), | ||
| }; | ||
|
|
||
| eventToUpdate = { | ||
| ...eventToUpdate, | ||
| attendees: updatedAttendees, | ||
| sendUpdates: event.response.sendUpdate ? "all" : "none", | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid dropping attendee edits when also changing response status
When event.response is provided, attendees are derived from existingEvent.attendees. If the UpdateEventInput also carries attendee changes (e.g., adding/removing guests), those edits may be overwritten. Prefer starting from the attendees in toGoogleCalendarEvent(event) when present, and then apply the self response mutation.
- if (event.response && event.response.status !== "unknown") {
- if (!existingEvent.attendees) {
+ if (event.response && event.response.status !== "unknown") {
+ const baseAttendees =
+ (toGoogleCalendarEvent(event) as any).attendees ??
+ existingEvent.attendees;
+ if (!baseAttendees) {
throw new Error("Event has no attendees");
}
- const selfIndex = existingEvent.attendees.findIndex(
+ const selfIndex = baseAttendees.findIndex(
(attendee) => attendee.self,
);
if (selfIndex === -1) {
throw new Error("User is not an attendee");
}
- const updatedAttendees = [...existingEvent.attendees];
+ const updatedAttendees = [...baseAttendees];
updatedAttendees[selfIndex] = {
...updatedAttendees[selfIndex],
responseStatus: toGoogleCalendarAttendeeResponseStatus(
event.response.status,
),
};
eventToUpdate = {
...eventToUpdate,
attendees: updatedAttendees,
sendUpdates: event.response.sendUpdate ? "all" : "none",
};
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Handle response status update within the same call for Google Calendar | |
| if (event.response && event.response.status !== "unknown") { | |
| if (!existingEvent.attendees) { | |
| throw new Error("Event has no attendees"); | |
| } | |
| const selfIndex = existingEvent.attendees.findIndex( | |
| (attendee) => attendee.self, | |
| ); | |
| if (selfIndex === -1) { | |
| throw new Error("User is not an attendee"); | |
| } | |
| const updatedAttendees = [...existingEvent.attendees]; | |
| updatedAttendees[selfIndex] = { | |
| ...updatedAttendees[selfIndex], | |
| responseStatus: toGoogleCalendarAttendeeResponseStatus( | |
| event.response.status, | |
| ), | |
| }; | |
| eventToUpdate = { | |
| ...eventToUpdate, | |
| attendees: updatedAttendees, | |
| sendUpdates: event.response.sendUpdate ? "all" : "none", | |
| }; | |
| } | |
| // Handle response status update within the same call for Google Calendar | |
| if (event.response && event.response.status !== "unknown") { | |
| const baseAttendees = | |
| (toGoogleCalendarEvent(event) as any).attendees ?? | |
| existingEvent.attendees; | |
| if (!baseAttendees) { | |
| throw new Error("Event has no attendees"); | |
| } | |
| const selfIndex = baseAttendees.findIndex( | |
| (attendee) => attendee.self, | |
| ); | |
| if (selfIndex === -1) { | |
| throw new Error("User is not an attendee"); | |
| } | |
| const updatedAttendees = [...baseAttendees]; | |
| updatedAttendees[selfIndex] = { | |
| ...updatedAttendees[selfIndex], | |
| responseStatus: toGoogleCalendarAttendeeResponseStatus( | |
| event.response.status, | |
| ), | |
| }; | |
| eventToUpdate = { | |
| ...eventToUpdate, | |
| attendees: updatedAttendees, | |
| sendUpdates: event.response.sendUpdate ? "all" : "none", | |
| }; | |
| } |
🤖 Prompt for AI Agents
In packages/api/src/providers/calendars/google-calendar.ts around lines 205 to
232, the code overwrites attendee edits by always starting from
existingEvent.attendees when event.response is present; instead, build the base
attendees from toGoogleCalendarEvent(event).attendees if the incoming
UpdateEventInput includes attendees, otherwise fall back to
existingEvent.attendees, then locate the self attendee and only mutate its
responseStatus; finally set eventToUpdate.attendees to this merged list and keep
sendUpdates logic unchanged so incoming attendee additions/removals are
preserved while updating the current user's response.
| if ( | ||
| error instanceof Error && | ||
| error.message.includes("Invalid sequence value") | ||
| ) { | ||
| // Re-fetch the event to get the latest sequence number and retry once | ||
| existingEvent = await this.client.calendars.events.retrieve(eventId, { | ||
| calendarId: calendar.id, | ||
| }); | ||
|
|
||
| let eventToUpdate = { | ||
| ...existingEvent, | ||
| calendarId: calendar.id, | ||
| ...toGoogleCalendarEvent(event), | ||
| // Use the fresh sequence number | ||
| sequence: existingEvent.sequence, | ||
| }; | ||
|
|
||
| // Handle response status update within the same call for Google Calendar | ||
| if (event.response && event.response.status !== "unknown") { | ||
| if (!existingEvent.attendees) { | ||
| throw new Error("Event has no attendees"); | ||
| } | ||
|
|
||
| const selfIndex = existingEvent.attendees.findIndex( | ||
| (attendee) => attendee.self, | ||
| ); | ||
|
|
||
| if (selfIndex === -1) { | ||
| throw new Error("User is not an attendee"); | ||
| } | ||
|
|
||
| const updatedAttendees = [...existingEvent.attendees]; | ||
| updatedAttendees[selfIndex] = { | ||
| ...updatedAttendees[selfIndex], | ||
| responseStatus: toGoogleCalendarAttendeeResponseStatus( | ||
| event.response.status, | ||
| ), | ||
| }; | ||
|
|
||
| eventToUpdate = { | ||
| ...eventToUpdate, | ||
| attendees: updatedAttendees, | ||
| sendUpdates: event.response.sendUpdate ? "all" : "none", | ||
| }; | ||
| } | ||
|
|
||
| const updatedEvent = await this.client.calendars.events.update( | ||
| eventId, | ||
| eventToUpdate, | ||
| ); | ||
|
|
||
| return parseGoogleCalendarEvent({ | ||
| calendar, | ||
| accountId: this.accountId, | ||
| event: updatedEvent, | ||
| }); | ||
| } | ||
|
|
||
| const updatedAttendees = [...existingEvent.attendees]; | ||
| updatedAttendees[selfIndex] = { | ||
| ...updatedAttendees[selfIndex], | ||
| responseStatus: toGoogleCalendarAttendeeResponseStatus( | ||
| event.response.status, | ||
| ), | ||
| }; | ||
|
|
||
| eventToUpdate = { | ||
| ...eventToUpdate, | ||
| attendees: updatedAttendees, | ||
| sendUpdates: event.response.sendUpdate ? "all" : "none", | ||
| }; | ||
| // Re-throw other errors | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Retry path: same attendee overwrite risk
The same overwrite risk exists in the retry branch; mirror the fix there to respect incoming attendee edits while updating self response.
- if (event.response && event.response.status !== "unknown") {
- if (!existingEvent.attendees) {
+ if (event.response && event.response.status !== "unknown") {
+ const baseAttendees =
+ (toGoogleCalendarEvent(event) as any).attendees ??
+ existingEvent.attendees;
+ if (!baseAttendees) {
throw new Error("Event has no attendees");
}
- const selfIndex = existingEvent.attendees.findIndex(
+ const selfIndex = baseAttendees.findIndex(
(attendee) => attendee.self,
);
if (selfIndex === -1) {
throw new Error("User is not an attendee");
}
- const updatedAttendees = [...existingEvent.attendees];
+ const updatedAttendees = [...baseAttendees];
updatedAttendees[selfIndex] = {
...updatedAttendees[selfIndex],
responseStatus: toGoogleCalendarAttendeeResponseStatus(
event.response.status,
),
};
eventToUpdate = {
...eventToUpdate,
attendees: updatedAttendees,
sendUpdates: event.response.sendUpdate ? "all" : "none",
};
}🤖 Prompt for AI Agents
In packages/api/src/providers/calendars/google-calendar.ts around lines 246 to
306, the retry branch currently overwrites attendees with
existingEvent.attendees when setting the user's response, which discards any
attendee edits coming in the incoming event; change the merge to prefer incoming
event.attendees (if provided) and only update the self attendee's
responseStatus, falling back to existingEvent.attendees when incoming attendees
are missing, and then set sendUpdates as before. Concretely: compute a
baseAttendees = event.attendees ?? existingEvent.attendees; if baseAttendees is
missing throw "Event has no attendees"; find selfIndex in baseAttendees, throw
if -1; create updatedAttendees = [...baseAttendees]; update only
updatedAttendees[selfIndex] by copying and setting responseStatus to
toGoogleCalendarAttendeeResponseStatus(event.response.status); then assign
eventToUpdate.attendees = updatedAttendees and eventToUpdate.sendUpdates =
event.response.sendUpdate ? "all" : "none" before calling
calendars.events.update.
| @@ -1,3 +1,4 @@ | |||
| import { randomUUID } from "node:crypto"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Consistent UUID source in API
Importing randomUUID from node:crypto at the top is correct. Ensure no remaining usages of crypto.randomUUID() remain in server code.
Run this to verify:
🏁 Script executed:
#!/bin/bash
# List any server-side references to crypto.randomUUID
rg -nP --type=ts -C2 '\bcrypto\.randomUUID\s*\(' packages/api -g '!**/node_modules/**'Length of output: 1066
Replace remaining crypto.randomUUID() calls with the imported randomUUID
The grep results show two direct usages of crypto.randomUUID() in packages/api/src/providers/conferencing/google-meet.ts that must be updated to use the top-of-file import { randomUUID } from "node:crypto" instead:
• packages/api/src/providers/conferencing/google-meet.ts:70
• packages/api/src/providers/conferencing/google-meet.ts:122
Please apply the following change at both locations:
- requestId: crypto.randomUUID(),
+ requestId: randomUUID(),This ensures a consistent UUID source across the API.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { randomUUID } from "node:crypto"; | |
| // In packages/api/src/providers/conferencing/google-meet.ts | |
| // — at around line 70 — | |
| requestId: randomUUID(), | |
| // — at around line 122 — | |
| requestId: randomUUID(), |
🤖 Prompt for AI Agents
In packages/api/src/providers/conferencing/google-meet.ts around lines 70 and
122, replace direct calls to crypto.randomUUID() with the already imported
randomUUID from "node:crypto"; update both usages to call randomUUID() so the
file uses the top-of-file import consistently and removes direct crypto
namespace access.
| try { | ||
| <<<<<<< HEAD | ||
| const updatedEvent = await this.client.calendars.events.update(eventId, { | ||
| calendarId, | ||
| ...existingEvent, | ||
| // Preserve the sequence number to prevent conflicts | ||
| sequence: existingEvent.sequence, | ||
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | ||
| conferenceData: { | ||
| createRequest: { | ||
| requestId: randomUUID(), | ||
| conferenceSolutionKey: { | ||
| type: "hangoutsMeet", | ||
| ======= | ||
| const updatedEvent = await this.client.calendars.events.update( | ||
| eventId, | ||
| { | ||
| calendarId, | ||
| ...existingEvent, | ||
| // Preserve the sequence number to prevent conflicts | ||
| sequence: existingEvent.sequence, | ||
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | ||
| conferenceData: { | ||
| createRequest: { | ||
| requestId: crypto.randomUUID(), | ||
| conferenceSolutionKey: { | ||
| type: "hangoutsMeet", | ||
| }, | ||
| >>>>>>> babd5bd43baca4599e85af543c72f1b832791cd4 | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| ); | ||
|
|
||
| if (!updatedEvent.conferenceData) { | ||
| throw new Error("Failed to create conference data"); | ||
| } | ||
| if (!updatedEvent.conferenceData) { | ||
| throw new Error("Failed to create conference data"); | ||
| } | ||
|
|
||
| return parseGoogleCalendarConferenceData(updatedEvent)!; | ||
| } catch (error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolve merge conflicts and standardize requestId generation
The file contains unresolved merge conflict markers, breaking the build and lint steps. Also, two competing implementations use crypto.randomUUID() vs randomUUID(). Keep node:crypto’s randomUUID consistently in the API layer.
Apply this diff to resolve the first conflict block and standardize on randomUUID:
@@
- try {
-<<<<<<< HEAD
- const updatedEvent = await this.client.calendars.events.update(eventId, {
- calendarId,
- ...existingEvent,
- // Preserve the sequence number to prevent conflicts
- sequence: existingEvent.sequence,
- conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE
- conferenceData: {
- createRequest: {
- requestId: randomUUID(),
- conferenceSolutionKey: {
- type: "hangoutsMeet",
-=======
- const updatedEvent = await this.client.calendars.events.update(
- eventId,
- {
- calendarId,
- ...existingEvent,
- // Preserve the sequence number to prevent conflicts
- sequence: existingEvent.sequence,
- conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE
- conferenceData: {
- createRequest: {
- requestId: crypto.randomUUID(),
- conferenceSolutionKey: {
- type: "hangoutsMeet",
- },
- },
- },
- },
- );
->>>>>>> babd5bd43baca4599e85af543c72f1b832791cd4
+ try {
+ const updatedEvent = await this.client.calendars.events.update(eventId, {
+ calendarId,
+ ...existingEvent,
+ // Preserve the sequence number to prevent conflicts
+ sequence: existingEvent.sequence,
+ conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE
+ conferenceData: {
+ createRequest: {
+ requestId: randomUUID(),
+ conferenceSolutionKey: {
+ type: "hangoutsMeet",
+ },
+ },
+ },
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| <<<<<<< HEAD | |
| const updatedEvent = await this.client.calendars.events.update(eventId, { | |
| calendarId, | |
| ...existingEvent, | |
| // Preserve the sequence number to prevent conflicts | |
| sequence: existingEvent.sequence, | |
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | |
| conferenceData: { | |
| createRequest: { | |
| requestId: randomUUID(), | |
| conferenceSolutionKey: { | |
| type: "hangoutsMeet", | |
| ======= | |
| const updatedEvent = await this.client.calendars.events.update( | |
| eventId, | |
| { | |
| calendarId, | |
| ...existingEvent, | |
| // Preserve the sequence number to prevent conflicts | |
| sequence: existingEvent.sequence, | |
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | |
| conferenceData: { | |
| createRequest: { | |
| requestId: crypto.randomUUID(), | |
| conferenceSolutionKey: { | |
| type: "hangoutsMeet", | |
| }, | |
| >>>>>>> babd5bd43baca4599e85af543c72f1b832791cd4 | |
| }, | |
| }, | |
| }, | |
| }, | |
| }); | |
| ); | |
| if (!updatedEvent.conferenceData) { | |
| throw new Error("Failed to create conference data"); | |
| } | |
| if (!updatedEvent.conferenceData) { | |
| throw new Error("Failed to create conference data"); | |
| } | |
| return parseGoogleCalendarConferenceData(updatedEvent)!; | |
| } catch (error) { | |
| try { | |
| const updatedEvent = await this.client.calendars.events.update(eventId, { | |
| calendarId, | |
| ...existingEvent, | |
| // Preserve the sequence number to prevent conflicts | |
| sequence: existingEvent.sequence, | |
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | |
| conferenceData: { | |
| createRequest: { | |
| requestId: randomUUID(), | |
| conferenceSolutionKey: { | |
| type: "hangoutsMeet", | |
| }, | |
| }, | |
| }, | |
| }); | |
| if (!updatedEvent.conferenceData) { | |
| throw new Error("Failed to create conference data"); | |
| } | |
| return parseGoogleCalendarConferenceData(updatedEvent)!; | |
| } catch (error) { | |
| // … |
🧰 Tools
🪛 Biome (2.1.2)
[error] 46-47: Expected a statement but instead found '<<<<<<< HEAD'.
Expected a statement here.
(parse)
[error] 58-59: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '==='.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 59-59: Expected an expression but instead found '==='.
Expected an expression here.
(parse)
[error] 59-59: Expected an expression but instead found '='.
Expected an expression here.
(parse)
[error] 57-59: Invalid assignment to { type: "hangoutsMeet", ======
This expression cannot be assigned to
(parse)
[error] 60-60: Expected an expression, or an assignment but instead found 'const'.
Expected an expression, or an assignment here.
(parse)
[error] 60-60: expected : but instead found updatedEvent
Remove updatedEvent
(parse)
[error] 73-74: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '>'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 74-74: Expected an expression but instead found '>>>'.
Expected an expression here.
(parse)
[error] 74-74: Expected an expression but instead found '>'.
Expected an expression here.
(parse)
[error] 77-77: Expected an expression but instead found '}'.
Expected an expression here.
(parse)
[error] 78-78: Expected a property, a shorthand property, a getter, a setter, or a method but instead found ')'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 60-60: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 60-60: Can't assign updatedEvent because it's a constant.
This is where the variable is defined as constant.
Unsafe fix: Replace const with let if you assign it to a new value.
(lint/correctness/noConstAssign)
🪛 GitHub Actions: Check formatting
[error] 47-47: Merge conflict marker encountered. Resolve merge conflicts and remove conflict markers (e.g., <<<<<<< HEAD, =======, >>>>>>>) to restore valid TypeScript syntax.
[error] 47-47: bun run prettier --check failed due to parsing error (Unexpected token) at line 47:0 in google-meet.ts. This is likely caused by the merge conflict markers.
🪛 GitHub Actions: Lint
[error] 47-47: ESLint: Parsing error: Merge conflict marker encountered. (Step: bun run lint in packages/api)
🤖 Prompt for AI Agents
In packages/api/src/providers/conferencing/google-meet.ts around lines 46 to 85,
remove the unresolved Git conflict markers and consolidate the event update call
to the formatted version (no <<<<>>> markers), using node:crypto's randomUUID()
for requestId; ensure the object structure matches the existingEvent merge and
conferenceData/createRequest shape, and add or keep the import for crypto (or
import { randomUUID } from "crypto" and call randomUUID()) so the code compiles
and lint passes.
| <<<<<<< HEAD | ||
| const updatedEvent = await this.client.calendars.events.update(eventId, { | ||
| calendarId, | ||
| ...freshEvent, | ||
| // Use the fresh sequence number | ||
| sequence: freshEvent.sequence, | ||
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | ||
| conferenceData: { | ||
| createRequest: { | ||
| requestId: randomUUID(), | ||
| conferenceSolutionKey: { | ||
| type: "hangoutsMeet", | ||
| ======= | ||
| const updatedEvent = await this.client.calendars.events.update( | ||
| eventId, | ||
| { | ||
| calendarId, | ||
| ...freshEvent, | ||
| // Use the fresh sequence number | ||
| sequence: freshEvent.sequence, | ||
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | ||
| conferenceData: { | ||
| createRequest: { | ||
| requestId: crypto.randomUUID(), | ||
| conferenceSolutionKey: { | ||
| type: "hangoutsMeet", | ||
| }, | ||
| >>>>>>> babd5bd43baca4599e85af543c72f1b832791cd4 | ||
| }, | ||
| }, | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolve second conflict block and keep retry idempotency with a new requestId
Same issue below. On the retry path, use a fresh requestId via node:crypto’s randomUUID() and remove conflict markers.
@@
-<<<<<<< HEAD
- const updatedEvent = await this.client.calendars.events.update(eventId, {
- calendarId,
- ...freshEvent,
- // Use the fresh sequence number
- sequence: freshEvent.sequence,
- conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE
- conferenceData: {
- createRequest: {
- requestId: randomUUID(),
- conferenceSolutionKey: {
- type: "hangoutsMeet",
-=======
- const updatedEvent = await this.client.calendars.events.update(
- eventId,
- {
- calendarId,
- ...freshEvent,
- // Use the fresh sequence number
- sequence: freshEvent.sequence,
- conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE
- conferenceData: {
- createRequest: {
- requestId: crypto.randomUUID(),
- conferenceSolutionKey: {
- type: "hangoutsMeet",
- },
- },
- },
- },
- );
->>>>>>> babd5bd43baca4599e85af543c72f1b832791cd4
+ const updatedEvent = await this.client.calendars.events.update(eventId, {
+ calendarId,
+ ...freshEvent,
+ // Use the fresh sequence number
+ sequence: freshEvent.sequence,
+ conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE
+ conferenceData: {
+ createRequest: {
+ requestId: randomUUID(),
+ conferenceSolutionKey: {
+ type: "hangoutsMeet",
+ },
+ },
+ },
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <<<<<<< HEAD | |
| const updatedEvent = await this.client.calendars.events.update(eventId, { | |
| calendarId, | |
| ...freshEvent, | |
| // Use the fresh sequence number | |
| sequence: freshEvent.sequence, | |
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | |
| conferenceData: { | |
| createRequest: { | |
| requestId: randomUUID(), | |
| conferenceSolutionKey: { | |
| type: "hangoutsMeet", | |
| ======= | |
| const updatedEvent = await this.client.calendars.events.update( | |
| eventId, | |
| { | |
| calendarId, | |
| ...freshEvent, | |
| // Use the fresh sequence number | |
| sequence: freshEvent.sequence, | |
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | |
| conferenceData: { | |
| createRequest: { | |
| requestId: crypto.randomUUID(), | |
| conferenceSolutionKey: { | |
| type: "hangoutsMeet", | |
| }, | |
| >>>>>>> babd5bd43baca4599e85af543c72f1b832791cd4 | |
| }, | |
| }, | |
| }, | |
| ); | |
| const updatedEvent = await this.client.calendars.events.update(eventId, { | |
| calendarId, | |
| ...freshEvent, | |
| // Use the fresh sequence number | |
| sequence: freshEvent.sequence, | |
| conferenceDataVersion: 1, // This ensures the conference data is created, DO NOT REMOVE | |
| conferenceData: { | |
| createRequest: { | |
| requestId: randomUUID(), | |
| conferenceSolutionKey: { | |
| type: "hangoutsMeet", | |
| }, | |
| }, | |
| }, | |
| }); |
🧰 Tools
🪛 Biome (2.1.2)
[error] 110-111: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '==='.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 111-111: Expected an expression but instead found '==='.
Expected an expression here.
(parse)
[error] 111-111: Expected an expression but instead found '='.
Expected an expression here.
(parse)
[error] 109-111: Invalid assignment to { type: "hangoutsMeet", ======
This expression cannot be assigned to
(parse)
[error] 112-112: Expected an expression, or an assignment but instead found 'const'.
Expected an expression, or an assignment here.
(parse)
[error] 112-112: expected : but instead found updatedEvent
Remove updatedEvent
(parse)
[error] 125-126: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '>'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 126-126: Expected an expression but instead found '>>>'.
Expected an expression here.
(parse)
[error] 126-126: Expected an expression but instead found '>'.
Expected an expression here.
(parse)
[error] 129-129: Expected an expression but instead found '}'.
Expected an expression here.
(parse)
[error] 130-130: Expected a property, a shorthand property, a getter, a setter, or a method but instead found ')'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 112-112: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 112-112: Can't assign updatedEvent because it's a constant.
This is where the variable is defined as constant.
Unsafe fix: Replace const with let if you assign it to a new value.
(lint/correctness/noConstAssign)
🤖 Prompt for AI Agents
In packages/api/src/providers/conferencing/google-meet.ts around lines 99 to
130, resolve the leftover merge conflict markers and ensure the retry path uses
a fresh requestId for idempotency by replacing the duplicated/conflicted
requestId usage with node:crypto.randomUUID(); remove the <<<>>> conflict
markers and the older randomUUID() import/usage if present, ensure import of
crypto.randomUUID (or use crypto.randomUUID() directly) is used consistently for
the createRequest.requestId so each retry generates a new idempotency key.
|
Thanks for bringing this to my attention. I've resolved the merge conflicts and standardized the UUID generation across the codebase. Here's what I did:
These changes should fix the sequence value errors by:
The code now correctly handles concurrent modifications by:
Is there anything else you'd like me to explain about these changes? |
Description
Resolve TRPC sequence value conflicts in Google Calendar API interactions by implementing a retry mechanism with event re-fetching.
Changes
updateEvent,acceptEvent,updateEventResponse, andcreateConferenceSummary by CodeRabbit
Bug Fixes
Refactor
Chores