Skip to main content
When an uncaught error is thrown inside your task, that attempt fails. Trigger.dev will then retry the task according to your configured retry settings. You can configure retrying in two ways:
  1. In your trigger.config file — sets the default for all tasks.
  2. On each individual task — overrides the default for that task.
By default, the CLI init command disables retrying in the DEV environment. You can enable it in your trigger.config file.

Retry configuration

Set retry options on a task using the retry field:
/trigger/task-with-retries.ts
import { task } from "@trigger.dev/sdk";

export const taskWithRetries = task({
  id: "task-with-retries",
  retry: {
    maxAttempts: 10,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
    randomize: true,
  },
  run: async (payload: any) => {
    // If this throws, it will retry up to 10 times
    await doSomethingThatMightFail();
  },
});
OptionDescription
maxAttemptsMaximum number of attempts before the run fails.
factorExponential backoff factor applied to each retry delay.
minTimeoutInMsMinimum delay between retries in milliseconds.
maxTimeoutInMsMaximum delay between retries in milliseconds.
randomizeAdds jitter to the retry delay to avoid thundering herd.

Combining tasks for reliability

Breaking work into smaller tasks gives each piece its own retry budget and lets you view and retry each part independently from the dashboard:
/trigger/multiple-tasks.ts
import { task } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  retry: { maxAttempts: 10 },
  run: async (payload: string) => {
    const result = await otherTask.triggerAndWait("some data");
    // ...do other stuff
  },
});

export const otherTask = task({
  id: "other-task",
  retry: { maxAttempts: 5 },
  run: async (payload: string) => {
    return { foo: "bar" };
  },
});

Retrying parts of a task

retry.onThrow()

Retry a specific block of code with its own retry settings. If all attempts fail, an error is thrown:
/trigger/retry-on-throw.ts
import { task, retry, logger } from "@trigger.dev/sdk";

export const retryOnThrow = task({
  id: "retry-on-throw",
  run: async (payload: any) => {
    const result = await retry.onThrow(
      async ({ attempt }) => {
        if (attempt < 3) throw new Error("not ready yet");
        return { foo: "bar" };
      },
      { maxAttempts: 3, randomize: false }
    );

    logger.info("Result", { result });
  },
});

retry.fetch()

Make HTTP requests with automatic retrying based on the response status or timeout:
/trigger/retry-fetch.ts
import { task, retry, logger } from "@trigger.dev/sdk";

export const taskWithFetchRetries = task({
  id: "task-with-fetch-retries",
  run: async (payload: any) => {
    // Retry on 429 using rate limit headers
    const headersResponse = await retry.fetch("https://api.example.com/endpoint", {
      retry: {
        byStatus: {
          "429": {
            strategy: "headers",
            limitHeader: "x-ratelimit-limit",
            remainingHeader: "x-ratelimit-remaining",
            resetHeader: "x-ratelimit-reset",
            resetFormat: "unix_timestamp_in_ms",
          },
        },
      },
    });
    const json = await headersResponse.json();
    logger.info("Response", { json });

    // Retry on 5xx errors with exponential backoff
    const backoffResponse = await retry.fetch("https://api.example.com/other", {
      retry: {
        byStatus: {
          "500-599": {
            strategy: "backoff",
            maxAttempts: 10,
            factor: 2,
            minTimeoutInMs: 1_000,
            maxTimeoutInMs: 30_000,
            randomize: false,
          },
        },
      },
    });
    const json2 = await backoffResponse.json();
    logger.info("Backoff response", { json2 });

    // Retry on timeout
    const timeoutResponse = await retry.fetch("https://api.example.com/slow", {
      timeoutInMs: 1000,
      retry: {
        timeout: {
          maxAttempts: 5,
          factor: 1.8,
          minTimeoutInMs: 500,
          maxTimeoutInMs: 30_000,
          randomize: false,
        },
      },
    });
    const json3 = await timeoutResponse.json();
    logger.info("Timeout response", { json3 });
  },
});

Advanced error handling with catchError

The catchError callback is called when an uncaught error is thrown inside run. Use it to inspect the error, modify retry behavior, or skip retrying entirely. If you do not return anything, the task’s default retry settings are used.

OpenAI error handling example

OpenAI errors require different handling depending on the reason for failure:
import { task } from "@trigger.dev/sdk";
import { OpenAI } from "openai";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export const openaiTask = task({
  id: "openai-task",
  retry: {
    maxAttempts: 10,
  },
  run: async (payload: { prompt: string }) => {
    const chatCompletion = await openai.chat.completions.create({
      messages: [{ role: "user", content: payload.prompt }],
      model: "gpt-4o",
    });

    return chatCompletion.choices[0].message.content;
  },
  catchError: async ({ payload, error, ctx, retryAt }) => {
    if (error instanceof OpenAI.APIError) {
      // No HTTP status — skip retrying
      if (!error.status) {
        return { skipRetrying: true };
      }

      // Out of credits — skip retrying
      if (error.status === 429 && error.type === "insufficient_quota") {
        return { skipRetrying: true };
      }

      // No headers — fall through to default retry logic
      if (!error.headers) {
        return;
      }

      const remainingRequests = error.headers["x-ratelimit-remaining-requests"];
      const requestResets = error.headers["x-ratelimit-reset-requests"];

      if (typeof remainingRequests === "string" && Number(remainingRequests) === 0) {
        return {
          retryAt: calculateResetAt(requestResets),
        };
      }
    }
  },
});

Preventing retries

AbortTaskRunError

Throw AbortTaskRunError to fail the run immediately without retrying:
/trigger/openai-task.ts
import { task, AbortTaskRunError } from "@trigger.dev/sdk";
import { OpenAI } from "openai";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export const openaiTask = task({
  id: "openai-task",
  run: async (payload: { prompt: string }) => {
    const chatCompletion = await openai.chat.completions.create({
      messages: [{ role: "user", content: payload.prompt }],
      model: "gpt-4o",
    });

    if (chatCompletion.choices[0]?.message.content === undefined) {
      // Abort — do not retry
      throw new AbortTaskRunError("OpenAI returned an empty response");
    }

    return chatCompletion.choices[0].message.content;
  },
});

Using try/catch

Catch errors yourself to implement fallback logic without triggering a retry:
/trigger/openai-with-fallback.ts
import { task } from "@trigger.dev/sdk";
import { OpenAI } from "openai";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export const openaiTask = task({
  id: "openai-task",
  run: async (payload: { prompt: string }) => {
    try {
      const chatCompletion = await openai.chat.completions.create({
        messages: [{ role: "user", content: payload.prompt }],
        model: "gpt-4o",
      });

      if (chatCompletion.choices[0]?.message.content === undefined) {
        throw new Error("OpenAI returned empty response");
      }

      return chatCompletion.choices[0].message.content;
    } catch (error) {
      // Fall back to a different model
      const fallback = await openai.chat.completions.create({
        messages: [{ role: "user", content: payload.prompt }],
        model: "gpt-3.5-turbo",
      });

      if (fallback.choices[0]?.message.content === undefined) {
        throw new Error("Fallback model also returned empty response");
      }

      return fallback.choices[0].message.content;
    }
  },
});

Build docs developers (and LLMs) love