Runlane
Recipes

Retries

Retry a temporary enrichment failure with bounded exponential backoff.

Use automatic task retries for external work that may succeed later and is safe to run again.

Retry a resilient enrichment job

The Runlane retry policy below is real. The marked fetchEnrichAndValidateRecord() call is pseudocode for your retrieval, AI enrichment, and validation work:

src/tasks/resilient-enrichment.ts
import { RetryBackoffType, task } from '@runlane/core'
import * as z from 'zod'

const enrichmentSchema = z.object({
  confidence: z.number().min(0).max(1),
  recordId: z.string(),
  summary: z.string(),
})

export const resilientEnrichment = task({
  id: 'data.resilient-enrichment',
  schema: z.object({ recordId: z.string() }),
  output: enrichmentSchema,
  retry: {
    maxAttempts: 5,
    backoff: {
      type: RetryBackoffType.Exponential,
      delay: '1s',
      maxDelay: '30s',
    },
  },
  async run({ recordId }, context) {
    return context.step.run('enrich-record', { output: enrichmentSchema }, async ({ token }) => {
      //////////////////////////////////
      // MAKE YOUR API CALLS HERE
      //////////////////////////////////
      const result = await fetchEnrichAndValidateRecord({
        idempotencyKey: token,
        recordId,
        signal: context.signal,
      })

      return enrichmentSchema.parse(result)
    })
  },
})

An uncaught temporary failure inside the durable step starts a later attempt according to the exponential backoff policy. maxAttempts includes the first attempt, so this task runs at most five times. Once the step succeeds, a later attempt can replay its validated result.

Runlane does not provide statement-level helpers equivalent to retry.fetch() or retry.onThrow(). A task-level retry re-enters the handler from the beginning.

Make repeats safe

An external action can succeed just before the worker stops. The next attempt may perform it again. Use provider idempotency or a durable step for operations that must resolve to one external action.

Do not retry invalid input, rejected authorization, missing required data, or other permanent failures. Throw new RunlaneError({ code: ErrorCode.TaskFailed, retryable: false }) at the point that recognizes the failure. Import both names from @runlane/core. The retry guide shows this in a complete task.

Before production

  • Keep the attempt count finite.
  • Add jitter or provider-aware rate limits outside Runlane when required.
  • Test the final exhausted attempt.
  • Confirm maintenance and delivery advance due retries.
  • Alert on repeated failures by stable error code.

Read retry failed work and save step results.

On this page