Skip to main content

Retry Strategies

Reliability

Configure intelligent retry behavior with exponential backoff and durable step patterns.

Exponential Backoff

Increasing delays between retries

Smart Retry Logic

Retry only transient failures

Durable Steps

Cache completed steps across retries

Failure Monitoring

Track and alert on failures

Retry Configuration

Configure how tasks should be retried when they fail. By default, tasks are retried up to 3 times with exponential backoff. Set the retry option on your task definition:

import { task } from '@sylphx/sdk/tasks'

export const processOrder = task({
  id: 'process-order',
  retry: {
    maxAttempts: 5,
    backoff: 'exponential',
  },
  run: async ({ orderId }) => {
    await chargeAndFulfill(orderId)
  },
})

Retry Options

PropertyTypeDescription
maxAttemptsnumber= 3Maximum number of retry attempts (1-10)
backoff"fixed" | "exponential"= "exponential"Backoff strategy between retries

Exponential Backoff

Exponential backoff increases the delay between retries, giving failing services time to recover while avoiding overwhelming them with requests.

Exponential backoff
import { task } from '@sylphx/sdk/tasks'

// Best for most use cases — prevents thundering herd
export const sendWebhook = task({
  id: 'send-webhook',
  retry: {
    maxAttempts: 5,
    backoff: 'exponential',
  },
  run: async ({ url, event }) => {
    await fetch(url, {
      method: 'POST',
      body: JSON.stringify({ event }),
    })
  },
})

Jitter

Sylphx automatically adds random jitter to retry delays. This prevents synchronized retries when multiple tasks fail at the same time.

Durable Steps — Skip Completed Work

The most powerful retry pattern is using step.run() inside your task. Completed steps are cached and skipped on retry — only the failed step reruns.

Durable steps
import { task } from '@sylphx/sdk/tasks'

export const processOrder = task({
  id: 'process-order',
  retry: { maxAttempts: 5, backoff: 'exponential' },
  run: async ({ orderId }, { step, attempt }) => {
    // If this task retries, completed steps are skipped
    const order = await step.run('fetch-order', () => getOrder(orderId))
    const reserved = await step.run('reserve-inventory', () =>
      reserveInventory(order.items)
    )
    await step.run('charge-payment', () =>
      chargePayment(order.total, reserved.reservationId)
    )
    await step.run('send-confirmation', () =>
      sendEmail({ to: order.email, template: 'order-confirmed' })
    )
  },
})

// If 'charge-payment' fails:
// Retry #2: fetch-order ✓ (cached), reserve-inventory ✓ (cached), charge-payment → retried
// No duplicate emails, no double-reservations

Handling Transient vs Permanent Failures

Distinguish between transient failures (retry) and permanent failures (don't retry) inside your task handler:

Proper error handling
import { task } from '@sylphx/sdk/tasks'

export const processOrder = task({
  id: 'process-order',
  retry: { maxAttempts: 5, backoff: 'exponential' },
  run: async ({ orderId }, { step }) => {
    const order = await step.run('fetch-order', () => getOrder(orderId))

    // Permanent failure — throw a non-retriable error
    if (!order) {
      throw Object.assign(new Error('Order not found'), { permanent: true })
    }

    if (order.status === 'cancelled') {
      throw Object.assign(new Error('Order is cancelled'), { permanent: true })
    }

    // Transient failure — just throw, task will retry
    await step.run('charge', () => chargePayment(order))
  },
})

Throw Errors for Transient Failures

Throw errors to signal transient failures — the Tasks SDK will retry according to your retry config. For permanent failures that should not retry, mark the error with permanent: true.

Best Practices

Make Tasks Idempotent

Tasks may run multiple times due to retries. Use step.run() and idempotency keys to prevent duplicate processing.

Use Durable Steps

Wrap side effects in step.run() so they're cached and not repeated on retry.

Set Appropriate Timeouts

Configure task timeouts to prevent stuck runs from blocking retries.

Monitor Failures

Use the Sylphx console to monitor task failure rates and identify systemic issues.

Idempotency Pattern

import { task } from '@sylphx/sdk/tasks'

export const processOrder = task({
  id: 'process-order',
  run: async ({ orderId }, { step }) => {
    // Check if already processed (idempotency guard)
    const existing = await step.run('check-existing', () =>
      db.processedOrders.findUnique({ where: { orderId } })
    )

    if (existing) {
      // Already processed — return cached result
      return { alreadyProcessed: true, result: existing }
    }

    // Process the order
    const result = await step.run('process', () => processOrder(orderId))

    // Record completion
    await step.run('record', () =>
      db.processedOrders.create({ data: { orderId, processedAt: new Date() } })
    )

    return result
  },
})