HangfireIO/Hangfire · error · InvalidOperationException
Was unable to initialize a background job '{ctx.BackgroundJo
Error message
Was unable to initialize a background job '{ctx.BackgroundJob.Id}', because it doesn't exist. What it means
InvalidOperationException thrown inside CoreBackgroundJobFactory.CreateBackgroundJobTwoSteps during a retry attempt (attempt > 0) when Connection.GetJobData(backgroundJob.Id) returns null. On retry, the factory re-checks that the job created by the previous (possibly committed) attempt still exists before applying its initial state. A null result means the job was never persisted or was already expired/removed — the factory cannot initialize state for a non-existent job, so it aborts. This typically surfaces when storage is flaky and the job row disappears between attempts, or when a custom storage's GetJobData implementation is incorrect.
Source
Thrown at src/Hangfire.Core/Client/CoreBackgroundJobFactory.cs:116
return null;
}
var backgroundJob = new BackgroundJob(jobId, context.Job, createdAt, parameters);
if (context.InitialState != null)
{
RetryOnException(ref attemptsLeft, static (attempt, ctx) =>
{
if (attempt > 0)
{
// Normally, a distributed lock should be applied when making a retry, since
// it's possible to get a timeout exception, when transaction was actually
// committed. But since background job can't be returned to a position where
// its state is null, and since only the current thread knows the job's identifier
// when its state is null, and since we shouldn't do anything when it's non-null,
// there will be no any race conditions.
var data = ctx.Context.Connection.GetJobData(ctx.BackgroundJob.Id);
if (data == null) throw new InvalidOperationException($"Was unable to initialize a background job '{ctx.BackgroundJob.Id}', because it doesn't exist.");
if (!String.IsNullOrEmpty(data.State)) return;
}
using (var transaction = ctx.Context.Connection.CreateWriteTransaction())
{
var applyContext = new ApplyStateContext(
ctx.Context.Storage,
ctx.Context.Connection,
transaction,
ctx.BackgroundJob,
ctx.Context.InitialState!,
oldStateName: null,
ctx.Context.Profiler,
ctx.StateMachine);
ctx.StateMachine.ApplyState(applyContext);
View on GitHub (pinned to c236dd0f93)
Solutions
- Set RetryAttempts to 0 (the default) if you do not need creation retries, eliminating the GetJobData re-check path.
- Investigate storage health and connectivity — verify GetJobData returns the job immediately after CreateExpiredJob on the same connection.
- Ensure no external process (cleanup, expiry, or a competing worker) removes jobs between creation and state initialization.
- If using a custom storage, verify GetJobData correctness for just-created job IDs.
Example fix
// before — retries trigger re-check that fails var factory = new BackgroundJobFactory(); ((CoreBackgroundJobFactory)factory._innerFactory).RetryAttempts = 3; // after — disable creation retries to avoid the path var factory = new BackgroundJobFactory(); // RetryAttempts stays at default 0
Defensive patterns
Strategy: retry
Validate before calling
// Validate storage connectivity and GetJobData consistency before relying on retries
var data = connection.GetJobData(testJobId);
if (data == null) throw new InvalidOperationException("Storage GetJobData returned null for a known job — investigate before enabling creation retries."); Try / catch
try { var job = factory.Create(context); }
catch (InvalidOperationException ex) when (ex.Message.Contains("because it doesn't exist"))
{ /* log, check storage health, and re-enqueue from the origin */ } Prevention
- Keep BackgroundJobFactory.RetryAttempts at 0 unless you have confirmed storage reliability.
- Monitor storage latency and connection failures; this error often follows a timeout on the first attempt.
- Ensure no cleanup/expiry process removes freshly-created jobs faster than the retry can re-check.
When it happens
Trigger: RetryAttempts > 0 and the first CreateExpiredJob attempt times out (transaction committed server-side but threw to the client); the second attempt's GetJobData returns null because the job expired, was deleted by a worker, or the storage connection points to a different database/node. Also reproducible with a misbehaving IStorageConnection.GetJobData.
Common situations: High-throughput environments where jobs expire or are processed before retry; storage connection failures mid-creation; Redis/SqlServer misconfiguration where GetJobData reads from a replica with replication lag; custom storage implementations that return null for freshly created jobs.
Related errors
- Background job creation failed. See inner exception for deta
- Attempts value must be equal or greater than zero.
- DelaysInSeconds value must be an array of non-negative numbe
- State change of a background job failed. See inner exception
- storage
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/c70b04f8eafd6610.
Report an issue: GitHub.