HangfireIO/Hangfire · error · ArgumentOutOfRangeException

Timeout value should be equal to or greater than zero.

Error message

Timeout value should be equal to or greater than zero.

What it means

ArgumentOutOfRangeException thrown by the ExpirationManager constructor when stateExpirationTimeout is less than TimeSpan.Zero. The background service that removes expired Hangfire records requires a non-negative grace window; a negative value is treated as a programming/config error. (Note: checkInterval has a stricter rule and must be strictly greater than zero.)

Source

Thrown at src/Hangfire.SqlServer/ExpirationManager.cs:59

        
        private static readonly string[] ProcessedTables =
        {
            "AggregatedCounter",
            "Job",
            "List",
            "Set",
            "Hash",
        };

        private readonly ILog _logger = LogProvider.For<ExpirationManager>();
        private readonly SqlServerStorage _storage;
        private readonly TimeSpan _stateExpirationTimeout;
        private readonly TimeSpan _checkInterval;

        public ExpirationManager(SqlServerStorage storage, TimeSpan stateExpirationTimeout, TimeSpan checkInterval)
        {
            if (storage == null) throw new ArgumentNullException(nameof(storage));
            if (stateExpirationTimeout < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(stateExpirationTimeout), "Timeout value should be equal to or greater than zero.");
            if (checkInterval <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(checkInterval), "Timeout value should be greater than zero.");

            _storage = storage;
            _stateExpirationTimeout = stateExpirationTimeout;
            _checkInterval = checkInterval;
        }

        public void Execute(CancellationToken cancellationToken)
        {
            ExecuteCore(_storage, cancellationToken);
        }

        public void Execute(BackgroundProcessContext context)
        {
            if (context.Storage is not SqlServerStorage storage)
            {
                return;
            }

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Clamp the stateExpirationTimeout to TimeSpan.Zero (or a sensible positive value) before constructing ExpirationManager / SqlServerStorageOptions.
  2. Validate config values at startup and throw a domain-specific message if negative.
  3. Use TimeSpan.Max(TimeSpan.Zero, computed) when deriving the timeout arithmetically.

Example fix

// before
var mgr = new ExpirationManager(storage, deadline - now, checkInterval);

// after
var mgr = new ExpirationManager(storage, TimeSpan.Max(TimeSpan.Zero, deadline - now), checkInterval);
Defensive patterns

Strategy: validation

Validate before calling

static TimeSpan AssertNonNegative(TimeSpan value, string name)
{
    if (value < TimeSpan.Zero)
        throw new ArgumentOutOfRangeException(name, "Value must be >= TimeSpan.Zero.");
    return value;
}

Prevention

When it happens

Trigger: Instantiating ExpirationManager (directly or via SqlServerStorage options that flow into it) with a negative stateExpirationTimeout TimeSpan.

Common situations: Computing the timeout as a difference that went negative (e.g. deadline - now when deadline already passed); reading a negative value from configuration; passing TimeSpan.FromTicks(-1) accidentally.

Understand the failure class

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/3d9207c9e168d5a3. Report an issue: GitHub.