{"record":{"id":"c70b04f8eafd6610","repo":"HangfireIO/Hangfire","slug":"was-unable-to-initialize-a-background-job-ctx-ba","errorCode":null,"errorMessage":"Was unable to initialize a background job '{ctx.BackgroundJob.Id}', because it doesn't exist.","messagePattern":"Was unable to initialize a background job '(.+?)', because it doesn't exist\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Hangfire.Core/Client/CoreBackgroundJobFactory.cs","lineNumber":116,"sourceCode":"                return null;\n            }\n\n            var backgroundJob = new BackgroundJob(jobId, context.Job, createdAt, parameters);\n\n            if (context.InitialState != null)\n            {\n                RetryOnException(ref attemptsLeft, static (attempt, ctx) =>\n                {\n                    if (attempt > 0)\n                    {\n                        // Normally, a distributed lock should be applied when making a retry, since\n                        // it's possible to get a timeout exception, when transaction was actually\n                        // committed. But since background job can't be returned to a position where\n                        // its state is null, and since only the current thread knows the job's identifier\n                        // when its state is null, and since we shouldn't do anything when it's non-null,\n                        // there will be no any race conditions.\n                        var data = ctx.Context.Connection.GetJobData(ctx.BackgroundJob.Id);\n                        if (data == null) throw new InvalidOperationException($\"Was unable to initialize a background job '{ctx.BackgroundJob.Id}', because it doesn't exist.\");\n\n                        if (!String.IsNullOrEmpty(data.State)) return;\n                    }\n\n                    using (var transaction = ctx.Context.Connection.CreateWriteTransaction())\n                    {\n                        var applyContext = new ApplyStateContext(\n                            ctx.Context.Storage,\n                            ctx.Context.Connection,\n                            transaction,\n                            ctx.BackgroundJob,\n                            ctx.Context.InitialState!,\n                            oldStateName: null,\n                            ctx.Context.Profiler,\n                            ctx.StateMachine);\n\n                        ctx.StateMachine.ApplyState(applyContext);\n","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/HangfireIO/Hangfire/blob/c236dd0f930f831ec151e436e138ddc429a02a72/src/Hangfire.Core/Client/CoreBackgroundJobFactory.cs#L98-L134","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — retries trigger re-check that fails\nvar factory = new BackgroundJobFactory();\n((CoreBackgroundJobFactory)factory._innerFactory).RetryAttempts = 3;\n\n// after — disable creation retries to avoid the path\nvar factory = new BackgroundJobFactory();\n// RetryAttempts stays at default 0","handlingStrategy":"retry","validationCode":"// Validate storage connectivity and GetJobData consistency before relying on retries\nvar data = connection.GetJobData(testJobId);\nif (data == null) throw new InvalidOperationException(\"Storage GetJobData returned null for a known job — investigate before enabling creation retries.\");","typeGuard":null,"tryCatchPattern":"try { var job = factory.Create(context); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"because it doesn't exist\"))\n{ /* log, check storage health, and re-enqueue from the origin */ }","preventionTips":["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."],"tags":["job-creation","retry","storage","race-condition"],"backgroundTag":null,"analyzedSha":"c236dd0f930f831ec151e436e138ddc429a02a72","analyzedAt":"2026-08-13T20:27:11.027Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}