HangfireIO/Hangfire · error · ArgumentException

The `timeOut` value must be positive.

Error message

The `timeOut` value must be positive.

What it means

ArgumentException thrown by SqlServerConnection.RemoveTimedOutServers when the provided timeOut is not equal to its own Duration() — i.e. it is negative. The cleanup deletes Server rows whose LastHeartbeat is older than now - timeOut; a negative timeOut would invert the logic (deleting active servers), so Hangfire rejects it up front.

Source

Thrown at src/Hangfire.SqlServer/SqlServerConnection.cs:571

                var affected = connection.Execute(
                    query,
                    new { id = serverId },
                    commandTimeout: storage.CommandTimeout);

                if (affected == 0)
                {
                    throw new BackgroundServerGoneException();
                }

                return affected;
            }, serverId);
        }

        public override int RemoveTimedOutServers(TimeSpan timeOut)
        {
            if (timeOut.Duration() != timeOut)
            {
                throw new ArgumentException("The `timeOut` value must be positive.", nameof(timeOut));
            }

            return _storage.UseConnection(_dedicatedConnection, static (storage, connection, timeout) => connection.Execute(
                storage.GetQueryFromTemplate(static schemaName =>
                    $@"delete s from [{schemaName}].Server s with (readpast, readcommitted) where LastHeartbeat < dateadd(ms, @timeoutMsNeg, sysutcdatetime())"),
                new { timeoutMsNeg = timeout.Negate().TotalMilliseconds },
                commandTimeout: storage.CommandTimeout), timeOut);
        }

        public override long GetSetCount(string key)
        {
            if (key == null) throw new ArgumentNullException(nameof(key));

            return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => connection.ExecuteScalar<long>(
                storage.GetQueryFromTemplate(static schemaName =>
                    $@"select count(*) from [{schemaName}].[Set] with (forceseek) where [Key] = @key"),
                new { key = key },
                commandTimeout: storage.CommandTimeout), key);

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Pass a positive TimeSpan (e.g. TimeSpan.FromMinutes(5)) to RemoveTimedOutServers.
  2. Validate the configured timeout at startup: if (timeout < TimeSpan.Zero) throw a clear config error.
  3. Guard with TimeSpan.Max(TimeSpan.Zero, timeout) when deriving the value arithmetically.

Example fix

// before
storage.RemoveTimedOutServers(now - recordedAt);

// after
storage.RemoveTimedOutServers(TimeSpan.FromMinutes(5));
Defensive patterns

Strategy: validation

Validate before calling

static TimeSpan AssertPositiveTimeout(TimeSpan t)
{
    if (t.Duration() != t) throw new ArgumentException("timeOut must be positive.", nameof(t));
    return t;
}

Prevention

When it happens

Trigger: Calling RemoveTimedOutServers with a negative TimeSpan (or calling .Negate() unintentionally before passing it).

Common situations: Computing timeOut from a config value that defaulted to a negative number; subtracting instead of adding; passing -configuredTimeout by mistake; reading a malformed setting.

Understand the failure class

Related errors


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