---
title: Retry Policy
description: Retry eligibility, fixed-delay vs backoff behavior, and failure lifecycle.
order: 34
---

# Retry Policy

When a job throws, DurableStack evaluates retry eligibility using the current attempt and `MaxAttempts`, then schedules the next try with either fixed delay or backoff.

## Retry rule

- Retry when `Attempt < MaxAttempts`.
- Reschedule for `UtcNow + calculated delay`.
- Mark terminal `failed` when max attempts is reached.

`MaxAttempts` includes the initial attempt. Example: `MaxAttempts = 3` allows one initial run + up to two retries.

## Retry behavior modes

DurableStack supports two retry modes:

- `FixedDelay` (default): every retry uses the same base delay.
- `Backoff`: each retry delay grows exponentially from the base delay (`baseDelay * 2^(Attempt - 1)`).

`Attempt` is the attempt that just failed. So with backoff and `baseDelay = 5s`:

- after attempt 1 fails: `5s`
- after attempt 2 fails: `10s`
- after attempt 3 fails: `20s`

## Where retry settings come from

Retry mode and initial delay can be set per job via `[DurableJob]`, and global limits/tuning come from `AddDurableStack` options.

### Per-job (attribute)

```csharp
using DurableStack.Core.Models;
using DurableStack.Hosting.DependencyInjection;

[DurableJob(
    Name = "send-digest-email",
    MaxAttempts = 5,
    RetryBehavior = RetryBehavior.Backoff,
    RetryInitialDelaySeconds = 10)]
public sealed class SendDigestEmailJob : IDurableJob
{
    public Task ExecuteAsync(JobContext context, CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }
}
```

- `RetryBehavior` default: `FixedDelay`
- `RetryInitialDelaySeconds` default: unset (`0` on attribute), which falls back to global `options.RetryDelay`

### Global runtime options

```csharp
builder.Services.AddDurableStack(options =>
{
    options.RetryDelay = TimeSpan.FromSeconds(5); // Default base delay when per-job initial delay is not set.
    options.RetryMaxDelay = TimeSpan.FromHours(1); // Upper cap applied to computed retry delay.
    options.RetryJitterEnabled = false; // Enables random jitter when true.
    options.RetryJitterRatio = 0.2; // +/- jitter ratio (0 to 1) when jitter is enabled.
});
```

## Delay calculation order

- Start with base delay:
  - per-job `RetryInitialDelaySeconds` if set and > 0
  - otherwise global `options.RetryDelay`
- Apply behavior:
  - `FixedDelay`: keep base delay
  - `Backoff`: exponential growth per failed attempt
- Apply jitter if enabled
- Clamp to `options.RetryMaxDelay` if exceeded

## Status progression

- `pending` -> `leased` -> `succeeded`
- `pending` -> `leased` -> `pending` (retry)
- `pending` -> `leased` -> `failed` (terminal)

## Guidance

- Use `FixedDelay` for predictable retry cadence.
- Use `Backoff` for transient dependency failures (API throttling, brief outages).
- Keep job handlers idempotent so retries are safe.
- Keep `MaxAttempts` lower for non-idempotent side effects.
- Monitor retry and failure trends in observability dashboards.
