Skip to main content

Overview

BullMQ provides a retry method that allows you to programmatically retry jobs that have already completed or failed. This is different from the automatic retry mechanism (configured via the attempts option) - the retry method lets you manually move a job back to the waiting queue at any time.
Only jobs in the completed or failed state can be retried. Active, waiting, or delayed jobs cannot be retried.

When to Use Job.retry()

The retry method is useful in scenarios such as:
  • Manual intervention: When a job failed due to a temporary external issue that has been resolved
  • Re-processing completed jobs: When you need to run a completed job again with the same data
  • Workflow recovery: When recovering from system failures or bugs that caused jobs to fail incorrectly

Basic Usage

import { Queue, Job } from 'bullmq';

const queue = new Queue('my-queue');

// Get a failed job by ID
const job = await Job.fromId(queue, 'job-id');

// Retry a failed job (default state is 'failed')
await job.retry();

// Retry a completed job
await job.retry('completed');
From src/classes/job.ts:1475:
/**
 * Attempts to retry the job. Only a job that has failed or completed can be retried.
 *
 * @param state - completed / failed
 * @param opts - options to retry a job
 * @returns A promise that resolves when the job has been successfully moved to the wait queue.
 * The queue emits a waiting event when the job is successfully moved.
 * @throws Will throw an error if the job does not exist, is locked, or is not in the expected state.
 */
async retry(
  state: FinishedStatus = 'failed',
  opts: RetryOptions = {},
): Promise<void> {
  await this.scripts.reprocessJob(this, state, opts);
  this.failedReason = null;
  this.finishedOn = null;
  this.processedOn = null;
  this.returnvalue = null;

  if (opts.resetAttemptsMade) {
    this.attemptsMade = 0;
  }

  if (opts.resetAttemptsStarted) {
    this.attemptsStarted = 0;
  }
}

Retry Options

The retry method accepts options to reset attempt counters, allowing the retried job to behave as if it’s being processed for the first time.

Reset Attempts Made

The attemptsMade counter tracks how many times a job has been processed. Resetting it allows the job to use its full retry allowance again.
// Retry and reset the attempts counter
await job.retry('failed', { resetAttemptsMade: true });
From src/classes/job.ts:115:
export class Job {
  /**
   * Number of attempts after the job has failed.
   * @defaultValue 0
   */
  attemptsMade = 0;
}

Reset Attempts Started

The attemptsStarted counter tracks how many times a job has been moved to the active state:
// Retry and reset both counters
await job.retry('failed', { 
  resetAttemptsMade: true,
  resetAttemptsStarted: true 
});
From src/classes/job.ts:107:
export class Job {
  /**
   * Number of attempts when job is moved to active.
   * @defaultValue 0
   */
  attemptsStarted = 0;
}

What Happens When You Retry

When a job is retried, the following occurs:
  1. Job is moved to waiting queue: The job is removed from the completed/failed set and added back to the waiting queue
  2. Properties are cleared: The following job properties are reset to null:
    • failedReason
    • finishedOn
    • processedOn
    • returnvalue
  3. Events are emitted: A waiting event is emitted when the job is successfully moved
  4. Parent dependencies restored: If the job is a child in a flow, its dependency relationship with the parent is restored
If you retry a job without resetting attemptsMade, and the job has already exhausted its retry attempts, it will fail immediately when processed again.

Error Handling

The retry method can fail in the following cases:
Error CodeDescription
-1Job does not exist
-3Job was not found in the expected state
try {
  await job.retry('failed');
} catch (error) {
  console.error('Failed to retry job:', error.message);
}

Automatic Retries vs Manual Retries

Automatic Retries

Configured via attempts option
await queue.add(
  'job',
  { data: 'test' },
  {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 1000,
    },
  }
);
Jobs automatically retry when they fail, up to the specified number of attempts.

Manual Retries

Using job.retry() method
const job = await Job.fromId(queue, 'job-id');
await job.retry('failed', {
  resetAttemptsMade: true
});
Manually retry a completed or failed job at any time, with full control over reset options.

Common Use Cases

When an external API or service is temporarily down:
const failedJobs = await queue.getFailed();

for (const job of failedJobs) {
  if (job.failedReason.includes('Service Unavailable')) {
    // Reset attempts to give it a fresh start
    await job.retry('failed', { resetAttemptsMade: true });
  }
}
After deploying a bug fix:
// Get all failed jobs from the last hour
const failedJobs = await queue.getFailed(0, 1000);
const oneHourAgo = Date.now() - 3600000;

for (const job of failedJobs) {
  if (job.finishedOn > oneHourAgo) {
    await job.retry('failed', {
      resetAttemptsMade: true,
      resetAttemptsStarted: true,
    });
  }
}
Process a completed job again (e.g., regenerate report):
const job = await Job.fromId(queue, 'report-123');

if (await job.isCompleted()) {
  // Rerun the job with the same data
  await job.retry('completed', {
    resetAttemptsMade: true,
    resetAttemptsStarted: true,
  });
}

Retry with Flows

When retrying a child job in a flow, the parent-child relationship is restored:
import { FlowProducer, Queue } from 'bullmq';

const flowProducer = new FlowProducer();
const parentQueue = new Queue('parent');
const childQueue = new Queue('child');

// Create a flow
const flow = await flowProducer.add({
  name: 'parent-job',
  queueName: 'parent',
  data: {},
  children: [
    {
      name: 'child-job',
      queueName: 'child',
      data: {},
    },
  ],
});

// If child fails and is retried
const childJob = await Job.fromId(childQueue, flow.children[0].job.id);
await childJob.retry('failed');

// The parent will wait for the child to complete again

Job Attempt Counters

From src/classes/job.ts:107-116:
export class Job {
  /**
   * Number of attempts when job is moved to active.
   * @defaultValue 0
   */
  attemptsStarted = 0;

  /**
   * Number of attempts after the job has failed.
   * @defaultValue 0
   */
  attemptsMade = 0;
}

Read More

Retry API Reference

View the complete Retry API documentation

Automatic Retries

Learn about automatic retry configuration with backoff strategies

Stop Retrying Jobs

How to prevent further retries using UnrecoverableError

Job States

Understanding job states and lifecycle

Build docs developers (and LLMs) love