---
title: "Errors & Retrying"
description: "How to deal with errors and write reliable tasks."
---

import OpenaiRetry from "/snippets/code/openai-retry.mdx";

When an uncaught error is thrown inside your task, that task attempt will fail.

You can configure retrying in two ways:

1. In your [trigger.config file](/config/config-file) you can set the default retrying behavior for all tasks.
2. On each task you can set the retrying behavior.

<Note>Task-level retry settings override the defaults in your `trigger.config` file.</Note>

<Note>
  By default when you create your project using the CLI init command we disabled retrying in the DEV
  environment. You can enable it in your [trigger.config file](/config/config-file).
</Note>

## A simple example with OpenAI

This task will retry 10 times with exponential backoff.

- `openai.chat.completions.create()` can throw an error.
- The result can be empty and we want to try again. So we manually throw an error.

<OpenaiRetry />

## Combining tasks

One way to gain reliability is to break your work into smaller tasks and [trigger](/triggering) them from each other. Each task can have its own retrying behavior:

```ts /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",
    };
  },
});
```

Another benefit of this approach is that you can view the logs and retry each task independently from the dashboard.

## Retrying smaller parts of a task

Another complimentary strategy is to perform retrying inside of your task.

We provide some useful functions that you can use to retry smaller parts of a task. Of course, you can also write your own logic or use other packages.

### retry.onThrow()

You can retry a block of code that can throw an error, with the same retry settings as a task.

```ts /trigger/retry-on-throw.ts
import { task, logger, retry } from "@trigger.dev/sdk";

export const retryOnThrow = task({
  id: "retry-on-throw",
  run: async (payload: any) => {
    //Will retry up to 3 times. If it fails 3 times it will throw.
    const result = await retry.onThrow(
      async ({ attempt }) => {
        //throw on purpose the first 2 times, obviously this is a contrived example
        if (attempt < 3) throw new Error("failed");
        //...
        return {
          foo: "bar",
        };
      },
      { maxAttempts: 3, randomize: false }
    );

    //this will log out after 3 attempts of retry.onThrow
    logger.info("Result", { result });
  },
});
```

<Note>
  If all of the attempts with `retry.onThrow` fail, an error will be thrown. You can catch this or
  let it cause a retry of the entire task.
</Note>

### retry.fetch()

You can use `fetch`, `axios`, or any other library in your code.

But we do provide a convenient function to perform HTTP requests with conditional retrying based on the response:

```ts /trigger/retry-fetch.ts
import { task, logger, retry } from "@trigger.dev/sdk";

export const taskWithFetchRetries = task({
  id: "task-with-fetch-retries",
  run: async ({ payload, ctx }) => {
    //if the Response is a 429 (too many requests), it will retry using the data from the response. A lot of good APIs send these headers.
    const headersResponse = await retry.fetch("http://my.host/test-headers", {
      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("Fetched headers response", { json });

    //if the Response is a 500-599 (issue with the server you're calling), it will retry up to 10 times with exponential backoff
    const backoffResponse = await retry.fetch("http://my.host/test-backoff", {
      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("Fetched backoff response", { json2 });

    //You can additionally specify a timeout. In this case if the response takes longer than 1 second, it will retry up to 5 times with exponential backoff
    const timeoutResponse = await retry.fetch("https://httpbin.org/delay/2", {
      timeoutInMs: 1000,
      retry: {
        timeout: {
          maxAttempts: 5,
          factor: 1.8,
          minTimeoutInMs: 500,
          maxTimeoutInMs: 30_000,
          randomize: false,
        },
      },
    });
    const json3 = await timeoutResponse.json();
    logger.info("Fetched timeout response", { json3 });

    return {
      result: "success",
      payload,
      json,
      json2,
      json3,
    };
  },
});
```

<Note>
  If all of the attempts with `retry.fetch` fail, an error will be thrown. You can catch this or let
  it cause a retry of the entire task.
</Note>

## Advanced error handling and retrying

We provide a `catchError` callback on the task and in your `trigger.config` file. This gets called when an uncaught error is thrown in your task.

You can

- Inspect the error, log it, and return a different error if you'd like.
- Modify the retrying behavior based on the error, payload, context, etc.

If you don't return anything from the function it will use the settings on the task (or inherited from the config). So you only need to use this to override things.

### OpenAI error handling example

OpenAI calls can fail for a lot of reasons and the ideal retry behavior is different for each.

In this complicated example:

- We skip retrying if there's no Response status.
- We skip retrying if you've run out of credits.
- If there are no Response headers we let the normal retrying logic handle it (return undefined).
- If we've run out of requests or tokens we retry at the time specified in the headers.

<CodeGroup>

```ts tasks.ts
import { task } from "@trigger.dev/sdk";
import { calculateISO8601DurationOpenAIVariantResetAt, openai } from "./openai.js";

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

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

      if (error.status === 429 && error.type === "insufficient_quota") {
        return {
          skipRetrying: true,
        };
      }

      if (!error.headers) {
        //returning undefined means the normal retrying logic will be used
        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: calculateISO8601DurationOpenAIVariantResetAt(requestResets),
        };
      }

      const remainingTokens = error.headers["x-ratelimit-remaining-tokens"];
      const tokensResets = error.headers["x-ratelimit-reset-tokens"];

      if (typeof remainingTokens === "string" && Number(remainingTokens) === 0) {
        return {
          retryAt: calculateISO8601DurationOpenAIVariantResetAt(tokensResets),
        };
      }
    }
  },
});
```

```ts openai.ts
import { OpenAI } from "openai";

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

export function calculateISO8601DurationOpenAIVariantResetAt(
  resets: string,
  now: Date = new Date()
): Date | undefined {
  // Check if the input is null or undefined
  if (!resets) return undefined;

  // Regular expression to match the duration string pattern
  const pattern = /^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+(?:\.\d+)?)s)?(?:(\d+)ms)?$/;
  const match = resets.match(pattern);

  // If the string doesn't match the expected format, return undefined
  if (!match) return undefined;

  // Extract days, hours, minutes, seconds, and milliseconds from the string
  const days = parseInt(match[1] ?? "0", 10) || 0;
  const hours = parseInt(match[2] ?? "0", 10) || 0;
  const minutes = parseInt(match[3] ?? "0", 10) || 0;
  const seconds = parseFloat(match[4] ?? "0") || 0;
  const milliseconds = parseInt(match[5] ?? "0", 10) || 0;

  // Calculate the future date based on the current date plus the extracted time
  const resetAt = new Date(now);
  resetAt.setDate(resetAt.getDate() + days);
  resetAt.setHours(resetAt.getHours() + hours);
  resetAt.setMinutes(resetAt.getMinutes() + minutes);
  resetAt.setSeconds(resetAt.getSeconds() + Math.floor(seconds));
  resetAt.setMilliseconds(
    resetAt.getMilliseconds() + (seconds - Math.floor(seconds)) * 1000 + milliseconds
  );

  return resetAt;
}
```

</CodeGroup>

## Preventing retries

### Using `AbortTaskRunError`

You can prevent retries by throwing an `AbortTaskRunError`. This will fail the task attempt and disable retrying.

```ts /trigger/myTasks.ts
import { task, AbortTaskRunError } from "@trigger.dev/sdk";

export const openaiTask = task({
  id: "openai-task",
  run: async (payload: { prompt: string }) => {
    //if this fails, it will throw an error and stop retrying
    const chatCompletion = await openai.chat.completions.create({
      messages: [{ role: "user", content: payload.prompt }],
      model: "gpt-3.5-turbo",
    });

    if (chatCompletion.choices[0]?.message.content === undefined) {
      // If OpenAI returns an empty response, abort retrying
      throw new AbortTaskRunError("OpenAI call failed");
    }

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

### Using try/catch

Sometimes you want to catch an error and don't want to retry the task. You can use try/catch as you normally would. In this example we fallback to using Replicate if OpenAI fails.

```ts /trigger/myTasks.ts
import { task } from "@trigger.dev/sdk";

export const openaiTask = task({
  id: "openai-task",
  run: async (payload: { prompt: string }) => {
    try {
      //if this fails, it will throw an error and retry
      const chatCompletion = await openai.chat.completions.create({
        messages: [{ role: "user", content: payload.prompt }],
        model: "gpt-3.5-turbo",
      });

      if (chatCompletion.choices[0]?.message.content === undefined) {
        //sometimes OpenAI returns an empty response, let's retry by throwing an error
        throw new Error("OpenAI call failed");
      }

      return chatCompletion.choices[0].message.content;
    } catch (error) {
      //use Replicate if OpenAI fails
      const prediction = await replicate.run(
        "meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
        {
          input: {
            prompt: payload.prompt,
            max_new_tokens: 250,
          },
        }
      );

      if (prediction.output === undefined) {
        //retry if Replicate fails
        throw new Error("Replicate call failed");
      }

      return prediction.output;
    }
  },
});
```

## Replay failed runs in bulk

After you deploy a fix, use [bulk actions](/bulk-actions) to replay multiple failed runs from the dashboard, or [bulk actions with the SDK](/runs/bulk-actions) to replay them from backend code. Bulk replay creates an asynchronous action that targets selected run IDs or a `runs.list()` filter, so you can retry a known failure set without replaying each run individually.
