Skip to content

Commit 8e8620c

Browse files
waleedlatif1claude
andcommitted
fix(polling): address PR review feedback for Google polling triggers
- Fix Drive cursor stall: use nextPageToken as resume point when breaking early from pagination instead of re-using the original token - Eliminate redundant Drive API call in Sheets poller by returning modifiedTime from the pre-check function - Add 403/429 rate-limit handling to Sheets API calls matching the Calendar handler pattern - Remove unused changeType field from DriveChangeEntry interface - Rename triggers/google_drive to triggers/google-drive for consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 34e1bd7 commit 8e8620c

File tree

5 files changed

+44
-23
lines changed

5 files changed

+44
-23
lines changed

apps/sim/lib/webhooks/polling/google-drive.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ interface GoogleDriveWebhookConfig {
2828
interface DriveChangeEntry {
2929
kind: string
3030
type: string
31-
changeType?: string
3231
time: string
3332
removed: boolean
3433
fileId: string
@@ -215,7 +214,8 @@ async function fetchChanges(
215214
): Promise<{ changes: DriveChangeEntry[]; newStartPageToken: string }> {
216215
const allChanges: DriveChangeEntry[] = []
217216
let currentPageToken = config.pageToken!
218-
let newStartPageToken = currentPageToken
217+
let newStartPageToken: string | undefined
218+
let lastNextPageToken: string | undefined
219219
const maxFiles = config.maxFilesPerPoll || MAX_FILES_PER_POLL
220220
let pages = 0
221221

@@ -252,18 +252,23 @@ async function fetchChanges(
252252
newStartPageToken = data.newStartPageToken as string
253253
}
254254

255-
// Stop if no more pages or we have enough changes.
256-
// Always use newStartPageToken (not nextPageToken) as the resume point —
257-
// nextPageToken paginates the current query but newStartPageToken is the
258-
// correct cursor for the next poll cycle.
255+
if (data.nextPageToken) {
256+
lastNextPageToken = data.nextPageToken as string
257+
}
258+
259259
if (!data.nextPageToken || allChanges.length >= maxFiles || pages >= MAX_PAGES) {
260260
break
261261
}
262262

263263
currentPageToken = data.nextPageToken as string
264264
}
265265

266-
return { changes: allChanges.slice(0, maxFiles), newStartPageToken }
266+
// If we exhausted all pages the API returns newStartPageToken on the final page.
267+
// If we broke early, fall back to the last nextPageToken so we resume from where
268+
// we stopped rather than re-fetching from the original cursor.
269+
const resumeToken = newStartPageToken ?? lastNextPageToken ?? config.pageToken!
270+
271+
return { changes: allChanges.slice(0, maxFiles), newStartPageToken: resumeToken }
267272
}
268273

269274
function filterChanges(

apps/sim/lib/webhooks/polling/google-sheets.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const googleSheetsPollingHandler: PollingProviderHandler = {
6161
}
6262

6363
// Pre-check: use Drive API to see if the file was modified since last poll
64-
const skipPoll = await isDriveFileUnchanged(
64+
const { unchanged: skipPoll, currentModifiedTime } = await isDriveFileUnchanged(
6565
accessToken,
6666
config.spreadsheetId,
6767
config.lastModifiedTime,
@@ -80,13 +80,6 @@ export const googleSheetsPollingHandler: PollingProviderHandler = {
8080
return 'success'
8181
}
8282

83-
// Get current Drive modifiedTime for state update
84-
const currentModifiedTime = await getDriveFileModifiedTime(
85-
accessToken,
86-
config.spreadsheetId,
87-
logger
88-
)
89-
9083
// Fetch current row count via column A
9184
const currentRowCount = await getDataRowCount(
9285
accessToken,
@@ -232,16 +225,16 @@ async function isDriveFileUnchanged(
232225
lastModifiedTime: string | undefined,
233226
requestId: string,
234227
logger: ReturnType<typeof import('@sim/logger').createLogger>
235-
): Promise<boolean> {
236-
if (!lastModifiedTime) return false
228+
): Promise<{ unchanged: boolean; currentModifiedTime?: string }> {
229+
if (!lastModifiedTime) return { unchanged: false }
237230

238231
try {
239232
const currentModifiedTime = await getDriveFileModifiedTime(accessToken, spreadsheetId, logger)
240-
return currentModifiedTime === lastModifiedTime
233+
return { unchanged: currentModifiedTime === lastModifiedTime, currentModifiedTime }
241234
} catch (error) {
242235
// If Drive check fails, proceed with Sheets API (don't skip)
243236
logger.warn(`[${requestId}] Drive modifiedTime check failed, proceeding with Sheets API`)
244-
return false
237+
return { unchanged: false }
245238
}
246239
}
247240

@@ -278,9 +271,17 @@ async function getDataRowCount(
278271
})
279272

280273
if (!response.ok) {
274+
const status = response.status
281275
const errorData = await response.json().catch(() => ({}))
276+
277+
if (status === 403 || status === 429) {
278+
throw new Error(
279+
`Sheets API rate limit (${status}) — skipping to retry next poll cycle: ${JSON.stringify(errorData)}`
280+
)
281+
}
282+
282283
throw new Error(
283-
`Failed to fetch row count: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`
284+
`Failed to fetch row count: ${status} ${response.statusText} - ${JSON.stringify(errorData)}`
284285
)
285286
}
286287

@@ -312,7 +313,14 @@ async function fetchHeaderRow(
312313
})
313314

314315
if (!response.ok) {
315-
logger.warn(`[${requestId}] Failed to fetch header row, proceeding without headers`)
316+
const status = response.status
317+
if (status === 403 || status === 429) {
318+
logger.warn(
319+
`[${requestId}] Sheets API rate limit (${status}) fetching header row, proceeding without headers`
320+
)
321+
} else {
322+
logger.warn(`[${requestId}] Failed to fetch header row, proceeding without headers`)
323+
}
316324
return []
317325
}
318326

@@ -344,9 +352,17 @@ async function fetchRowRange(
344352
})
345353

346354
if (!response.ok) {
355+
const status = response.status
347356
const errorData = await response.json().catch(() => ({}))
357+
358+
if (status === 403 || status === 429) {
359+
throw new Error(
360+
`Sheets API rate limit (${status}) — skipping to retry next poll cycle: ${JSON.stringify(errorData)}`
361+
)
362+
}
363+
348364
throw new Error(
349-
`Failed to fetch rows ${startRow}-${endRow}: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`
365+
`Failed to fetch rows ${startRow}-${endRow}: ${status} ${response.statusText} - ${JSON.stringify(errorData)}`
350366
)
351367
}
352368

apps/sim/triggers/registry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ import {
9090
} from '@/triggers/github'
9191
import { gmailPollingTrigger } from '@/triggers/gmail'
9292
import { gongCallCompletedTrigger, gongWebhookTrigger } from '@/triggers/gong'
93-
import { googleDrivePollingTrigger } from '@/triggers/google_drive'
93+
import { googleDrivePollingTrigger } from '@/triggers/google-drive'
9494
import { googleCalendarPollingTrigger } from '@/triggers/google-calendar'
9595
import { googleSheetsPollingTrigger } from '@/triggers/google-sheets'
9696
import { googleFormsWebhookTrigger } from '@/triggers/googleforms'

0 commit comments

Comments
 (0)