From 4be05343774c1592e2a0ab60a229500dcae4f1dd Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Wed, 1 Apr 2026 10:57:20 +0545 Subject: [PATCH 1/2] perf(OUT-3487): add delayed retry backoff for trigger.dev sync tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add RETRY_CONFIG (3 attempts, 2-5 min exponential backoff with jitter) to per-file sync tasks: syncDropboxFileToAssembly, syncAssemblyFileToDropbox, deleteDropboxFileInAssembly, updateDropboxFileInAssembly - Disable retry on orchestrator tasks (initiateDropboxToAssemblySync, initiateAssemblyToDropboxSync, bidirectionalMasterSync) to prevent full re-runs on single file failures - Simplify bidirectionalMasterSync — remove error collection pattern, let errors propagate with original stack trace - Add taskId and attemptNumber tags to Sentry onFailure for easier filtering and tracing Co-Authored-By: Claude Opus 4.6 (1M context) --- src/trigger/processFileSync.ts | 45 ++++++++++++++++++++-------------- trigger.config.ts | 4 +++ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/trigger/processFileSync.ts b/src/trigger/processFileSync.ts index 81abf23..98091b9 100644 --- a/src/trigger/processFileSync.ts +++ b/src/trigger/processFileSync.ts @@ -40,6 +40,14 @@ type HandleChannelFilePayload = { const machine = env.TRIGGER_MACHINE +const RETRY_CONFIG = { + maxAttempts: 3, + minTimeoutInMs: 120_000, // 2 minutes + maxTimeoutInMs: 300_000, // 5 minutes + factor: 2, + randomize: true, +} as const + export const processDropboxChanges = task({ id: 'process-dropbox-changes', machine, @@ -59,20 +67,24 @@ export const processDropboxChanges = task({ export const bidirectionalMasterSync = task({ id: 'bidirectional-master-sync', machine, + retry: { + maxAttempts: 0, + }, run: async (payload: SyncTaskPayload) => { - try { - await initiateAssemblyToDropboxSync.triggerAndWait(payload) - logger.info('\n\n Synced Assembly files to Dropbox \n\n') - await initiateDropboxToAssemblySync.triggerAndWait(payload) - } catch (error: unknown) { - logger.error('processFileSync#bidirectionalMasterSync', { error }) - } + await initiateAssemblyToDropboxSync.triggerAndWait(payload) + logger.info('Synced Assembly files to Dropbox') + + await initiateDropboxToAssemblySync.triggerAndWait(payload) + logger.info('Synced Dropbox files to Assembly') }, }) export const initiateDropboxToAssemblySync = task({ id: 'initiate-dropbox-to-assembly-sync', machine, + retry: { + maxAttempts: 0, + }, run: async (payload: SyncTaskPayload) => { logger.info( 'processFileSync#initiateDropboxToAssemblySync. Syncing files from Dropbox to Assembly', @@ -154,9 +166,7 @@ export const syncDropboxFileToAssembly = task({ name: 'sync-dropbox-file-to-assembly', concurrencyLimit: 25, }, - retry: { - maxAttempts: 3, - }, + retry: RETRY_CONFIG, run: (payload: DropboxToAssemblySyncFilesPayload) => { logger.info('processFileSync#syncDropboxFileToAssembly') return withErrorLogging(payload, async () => { @@ -283,9 +293,7 @@ export const deleteDropboxFileInAssembly = task({ name: 'delete-dropbox-file-in-assembly', concurrencyLimit: 1, }, - retry: { - maxAttempts: 3, - }, + retry: RETRY_CONFIG, run: async (payload: DropboxToAssemblySyncFilesPayload) => { const { opts, entry } = payload const { channelSyncId, user, connectionToken, dbxRootPath } = opts @@ -300,9 +308,7 @@ export const updateDropboxFileInAssembly = task({ name: 'update-dropbox-file-in-assembly', concurrencyLimit: 5, }, - retry: { - maxAttempts: 3, - }, + retry: RETRY_CONFIG, run: async (payload: DropboxToAssemblySyncFilesPayload) => { await deleteDropboxFileInAssembly.triggerAndWait(payload) await syncDropboxFileToAssembly.trigger(payload) @@ -312,6 +318,9 @@ export const updateDropboxFileInAssembly = task({ export const initiateAssemblyToDropboxSync = task({ id: 'initiate-assembly-to-dropbox-sync', machine, + retry: { + maxAttempts: 0, + }, run: async (payload: SyncTaskPayload) => { logger.info( 'processFileSync#initiateAssemblyToDropboxSync. Syncing files from Assembly to Dropbox', @@ -356,9 +365,7 @@ export const syncAssemblyFileToDropbox = task({ name: 'sync-assembly-file-to-dropbox', concurrencyLimit: 25, }, - retry: { - maxAttempts: 3, - }, + retry: RETRY_CONFIG, run: (payload: AssemblyToDropboxSyncFilesPayload) => { logger.info('processFileSync#syncAssemblyFileToDropbox') return withErrorLogging(payload, async () => { diff --git a/trigger.config.ts b/trigger.config.ts index 18d0b2c..b6c0a9f 100644 --- a/trigger.config.ts +++ b/trigger.config.ts @@ -69,6 +69,10 @@ export default defineConfig({ onFailure: ({ payload, error, ctx }) => { console.error({ payload, error, ctx }) Sentry.captureException(error, { + tags: { + taskId: ctx.task.id, + attemptNumber: ctx.attempt.number, + }, extra: { payload, ctx, From 52d356d6e9fc292ad5a3361b10124fd141a5b00b Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Wed, 1 Apr 2026 12:58:31 +0545 Subject: [PATCH 2/2] docs(OUT-3487): add retry config doc used for trigger.dev task --- src/trigger/processFileSync.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/trigger/processFileSync.ts b/src/trigger/processFileSync.ts index 98091b9..28309f7 100644 --- a/src/trigger/processFileSync.ts +++ b/src/trigger/processFileSync.ts @@ -40,6 +40,17 @@ type HandleChannelFilePayload = { const machine = env.TRIGGER_MACHINE +/** + * Retry config for per-file sync tasks (e.g., syncDropboxFileToAssembly, deleteDropboxFileInAssembly). + * Delays retries to give the DB time to recover from transient failures (connection issues, timeouts). + * + * - maxAttempts: Number of retry attempts after the initial failure. + * - minTimeoutInMs: Initial wait before the first retry (2 min). + * - maxTimeoutInMs: Upper cap on retry delay (5 min). + * - factor: Exponential backoff multiplier. Each retry waits factor × previous delay + * (e.g., 2 min → 4 min → 5 min capped). + * - randomize: Adds jitter (±50%) to prevent multiple failed tasks from retrying simultaneously. + */ const RETRY_CONFIG = { maxAttempts: 3, minTimeoutInMs: 120_000, // 2 minutes