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:
-
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
}
-
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
})
}
}
-
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:
-
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 })
})
-
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;
}
-
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
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
Root Cause Analysis
The core issue stems from missing error handling and unhandled promise rejections in the asynchronous operations of the
pod-calendarservice:Unhandled Promise in
main.ts(Webhook Handler):The
/event(POST) webhook triggersOutcomingClient.pushasynchronously but does not wait for or handle the result correctly. If an exception occurs within thepushmethod, it gets lost because there is nocatchblock on the promise chain.Missing
try/catchon Google API Calls:Within the
OutcomingClientclass, specifically in thecreatemethod, the call tothis.calendar.events.insertis not wrapped in atry/catchblock. 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.Missing Logs for Bouncing Syncs:
Methods like
getTokenByEventreturn 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:
Add
catchBlock to the Webhook Trigger:Wrap Google API Calls in
try/catch:In
outcomingClient.ts, API requests (insert,update) should trace exceptions and throw them.Enhance Trace Logging:
Add
ctx.warnlogs whengetTokenByEventfails or when noExternalCalendaris linked.Environment Details:
pod-calendar,server-plugins/calendar-resourcesservices/calendar/pod-calendar/src/main.ts,services/calendar/pod-calendar/src/outcomingClient.ts