HangfireIO/Hangfire · error · InvalidOperationException

Can not add a continuation: parent background job '{parentId

Error message

Can not add a continuation: parent background job '{parentId}' does not exist.

What it means

ContinuationsSupportAttribute (ContinuationsSupportAttribute.cs:135) stores continuation chains as a parameter on the parent job. Before appending a continuation it loads the parent's JobData; if the parent does not exist (was deleted, expired, or never created), it throws to prevent creating a dangling/orphaned continuation that could never fire.

Source

Thrown at src/Hangfire.Core/ContinuationsSupportAttribute.cs:135

        private void AddContinuation(ElectStateContext context, AwaitingState awaitingState)
        {
            var connection = context.Connection;
            var parentId = awaitingState.ParentId;

            // We store continuations as a json array in a job parameter. Since there 
            // is no way to add a continuation in an atomic way, we are placing a 
            // distributed lock on parent job to prevent race conditions, when
            // multiple threads add continuation to the same parent job.
            using (connection.AcquireDistributedJobLock(parentId, AddJobLockTimeout))
            {
                var jobData = connection.GetJobData(parentId);
                if (jobData == null)
                {
                    // When we try to add a continuation for a removed job,
                    // the system should throw an exception instead of creating
                    // corrupted state.
                    throw new InvalidOperationException(
                        $"Can not add a continuation: parent background job '{parentId}' does not exist.");
                }

                var continuations = GetContinuations(context, parentId);

                // Continuation may be already added. This may happen, when outer transaction
                // was failed after adding a continuation last time, since the addition is
                // performed outside of an outer transaction.
                if (!continuations.Exists(x => x.JobId == context.BackgroundJob.Id))
                {
                    continuations.Add(new Continuation { JobId = context.BackgroundJob.Id, Options = awaitingState.Options });

                    // Set continuation only after ensuring that parent job still
                    // exists. Otherwise we could create add non-expiring (garbage)
                    // parameter for the parent job.
                    SetContinuations(connection, parentId, continuations);
                }

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Verify the parent job exists via monitoringApi.JobDetails(parentId) or connection.GetJobData(parentId) before calling ContinueJobWith.
  2. Register the continuation immediately after enqueueing the parent (capture the returned ID) rather than using a stored/external ID.
  3. Increase or disable job expiration if continuations are added asynchronously long after the parent ran.
  4. Wrap the call in a try/catch if a missing parent is a recoverable condition and handle gracefully.

Example fix

// before
var parentId = GetCachedJobId(); // may be stale
BackgroundJob.ContinueJobWith(parentId, () => FollowUp());

// after
var parentId = BackgroundJob.Enqueue(() => Parent());
// immediately chain
BackgroundJob.ContinueJobWith(parentId, () => FollowUp());
Defensive patterns

Strategy: validation

Validate before calling

using (var conn = JobStorage.Current.GetConnection())
{
    var data = conn.GetJobData(parentId);
    if (data == null)
    {
        // parent missing — do not call ContinueJobWith
        logger.Warn($"Parent job {parentId} not found; skipping continuation.");
        return;
    }
}
BackgroundJob.ContinueJobWith(parentId, () => FollowUp());

Try / catch

try
{
    BackgroundJob.ContinueJobWith(parentId, () => FollowUp());
}
catch (InvalidOperationException ex) when (ex.Message.Contains("does not exist"))
{
    logger.Warn($"Parent job {parentId} was removed before continuation registered.");
    // optionally re-enqueue parent or alert
}

Prevention

When it happens

Trigger: Calling BackgroundJob.ContinueJobWith(parentId, ...) where parentId refers to a job that was already deleted, removed by retention, or was never enqueued. Also occurs when the parent job ID is malformed or belongs to a different storage.

Common situations: Parent job completed and was cleaned up by a retention job before the continuation was registered; passing a hardcoded or stale job ID; multi-tenant setups where the ID belongs to another tenant's storage; race with manual job deletion.

Related errors


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