diff --git a/apps/sim/lib/webhooks/polling/google-drive.ts b/apps/sim/lib/webhooks/polling/google-drive.ts index b4ecc06941..d6c75b5538 100644 --- a/apps/sim/lib/webhooks/polling/google-drive.ts +++ b/apps/sim/lib/webhooks/polling/google-drive.ts @@ -252,20 +252,32 @@ async function fetchChanges( newStartPageToken = data.newStartPageToken as string } - if (data.nextPageToken) { - lastNextPageToken = data.nextPageToken as string - } + // Only advance the resume cursor when we'll actually use all changes from this page. + // If allChanges exceeds maxFiles, we'll slice off the extras — so we must NOT + // advance past this page, otherwise the sliced changes are lost permanently. + const hasMore = !!data.nextPageToken + const overLimit = allChanges.length >= maxFiles - if (!data.nextPageToken || allChanges.length >= maxFiles || pages >= MAX_PAGES) { + if (!hasMore || overLimit || pages >= MAX_PAGES) { + // If we stopped mid-stream and haven't consumed all changes from this page, + // keep currentPageToken so the next poll re-fetches this page. + // If we consumed everything on this page but there are more pages, + // advance to nextPageToken so we don't re-process this page. + if (hasMore && !overLimit) { + lastNextPageToken = data.nextPageToken as string + } else if (hasMore && overLimit && allChanges.length > maxFiles) { + // We got more changes than maxFiles from this page — don't advance, + // re-fetch this page next time (idempotency deduplicates already-processed ones) + } else if (hasMore) { + lastNextPageToken = data.nextPageToken as string + } break } + lastNextPageToken = data.nextPageToken as string currentPageToken = data.nextPageToken as string } - // If we exhausted all pages the API returns newStartPageToken on the final page. - // If we broke early, fall back to the last nextPageToken so we resume from where - // we stopped rather than re-fetching from the original cursor. const resumeToken = newStartPageToken ?? lastNextPageToken ?? config.pageToken! return { changes: allChanges.slice(0, maxFiles), newStartPageToken: resumeToken } diff --git a/apps/sim/lib/webhooks/polling/google-sheets.ts b/apps/sim/lib/webhooks/polling/google-sheets.ts index 0afe61d9a5..f62d8f42d8 100644 --- a/apps/sim/lib/webhooks/polling/google-sheets.ts +++ b/apps/sim/lib/webhooks/polling/google-sheets.ts @@ -315,12 +315,12 @@ async function fetchHeaderRow( if (!response.ok) { const status = response.status if (status === 403 || status === 429) { - logger.warn( - `[${requestId}] Sheets API rate limit (${status}) fetching header row, proceeding without headers` + const errorData = await response.json().catch(() => ({})) + throw new Error( + `Sheets API rate limit (${status}) fetching header row — skipping to retry next poll cycle: ${JSON.stringify(errorData)}` ) - } else { - logger.warn(`[${requestId}] Failed to fetch header row, proceeding without headers`) } + logger.warn(`[${requestId}] Failed to fetch header row, proceeding without headers`) return [] } diff --git a/apps/sim/triggers/google-drive/poller.ts b/apps/sim/triggers/google-drive/poller.ts index 3169a794df..51289233a7 100644 --- a/apps/sim/triggers/google-drive/poller.ts +++ b/apps/sim/triggers/google-drive/poller.ts @@ -1,9 +1,6 @@ -import { createLogger } from '@sim/logger' import { GoogleDriveIcon } from '@/components/icons' import type { TriggerConfig } from '@/triggers/types' -const logger = createLogger('GoogleDrivePollingTrigger') - const MIME_TYPE_OPTIONS = [ { id: '', label: 'All Files' }, { id: 'application/vnd.google-apps.document', label: 'Google Docs' },