Skip to content

Bug: Huly to Google Calendar Outbound Sync Fails Silently (Unhandled Promise Rejections) #10535

@bunyaminkalkan

Description

@bunyaminkalkan

Problem Description

Events created or updated via the Huly interface are not synced to the connected Google Calendar (outbound sync fails).

While inbound events (from Google to Huly) sync correctly, outbound events fail to sync without producing any error logs in the server or UI. This silent failure makes it extremely difficult to identify the underlying cause of the synchronization issue (e.g., API rejection, payload formatting errors, timezone mismatch, etc.).

Environment

  • Version: v0.7.353 (hardcoreeng/* images)
  • Deployment: Dokploy (docker-compose)

Root Cause Analysis

The core issue stems from missing error handling and unhandled promise rejections in the asynchronous operations of the pod-calendar service:

  1. Unhandled Promise in main.ts (Webhook Handler):
    The /event (POST) webhook triggers OutcomingClient.push asynchronously but does not wait for or handle the result correctly. If an exception occurs within the push method, it gets lost because there is no catch block on the promise chain.

    // services/calendar/pod-calendar/src/main.ts
    handler: async (req, res) => {
      // ...
      // The error is completely swallowed here.
      void OutcomingClient.push(ctx, accountClient, workspace, event, type) 
      res.send() // Returns success prematurely
    }
  2. Missing try/catch on Google API Calls:
    Within the OutcomingClient class, specifically in the create method, the call to this.calendar.events.insert is not wrapped in a try/catch block. If the Google Calendar API returns an error (e.g., 400 Bad Request or 403 Forbidden), the exception bubbles up and gets lost in the unhandled promise mentioned above.

    // services/calendar/pod-calendar/src/outcomingClient.ts
    private async create(event: Event, calendar: ExternalCalendar): Promise<void> {
      const body = await this.convertBody(event)
      const calendarId = calendar.externalId
      if (calendarId !== undefined) {
        await this.rateLimiter.take(1)
        // If Google API rejects the payload, the error crashes the async flow silently.
        await this.calendar.events.insert({ 
          calendarId,
          requestBody: body
        })
      }
    }
  3. Missing Logs for Bouncing Syncs:
    Methods like getTokenByEvent return early if a token or calendar mapping is not found, but without throwing a warning or info log. This makes it impossible to distinguish between a missing token mapping and an actual Google API rejection.

Proposed Solution

To ensure the system works reliably and is transparent in case of outbound sync failures, proper error handling must be introduced:

  1. Add catch Block to the Webhook Trigger:

    void OutcomingClient.push(ctx, accountClient, workspace, event, type).catch((err: any) => {
      ctx.error('Outcoming sync failed internally', { eventId: event.eventId, error: err })
    })
  2. Wrap Google API Calls in try/catch:
    In outcomingClient.ts, API requests (insert, update) should trace exceptions and throw them.

    try {
      await this.calendar.events.insert({ calendarId, requestBody: body })
    } catch (err: any) {
      this.ctx.error('Google API Insert Error', { calendarId, error: err.message, body })
      throw err; 
    }
  3. Enhance Trace Logging:
    Add ctx.warn logs when getTokenByEvent fails or when no ExternalCalendar is linked.


Environment Details:

  • Services Details: pod-calendar, server-plugins/calendar-resources
  • Affected Files: services/calendar/pod-calendar/src/main.ts, services/calendar/pod-calendar/src/outcomingClient.ts

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions