elsa-workflows/elsa-core · error · Exception

Cannot transition from

Error message

Cannot transition from {SubStatus} to {subStatus}

What it means

WorkflowExecutionContext enforces a legal lifecycle for workflow sub-statuses. TransitionTo validates that the move from the current WorkflowSubStatus to the target is allowed; if not, this exception is thrown and the status is not changed. It guards the internal state machine against illegal or re-entrant status updates.

Solutions

  1. Inspect the workflow's current SubStatus before calling TransitionTo and only issue legal transitions (e.g. guard with workflowExecutionContext.CanTransitionTo(target)).
  2. Fix activity/host logic that schedules work or resumes bookmarks after the workflow reached a terminal status (Finished/Cancelled/Faulted).
  3. Serialize status-changing operations (use a single execution pipeline or locking) to prevent concurrent completions racing.
  4. Check whether a bookmark resume is stale (workflow instance already completed) before resuming, e.g. re-load instance state and skip if Finished.
  5. Verify the transition matrix matches your Elsa version; if using custom statuses, align with the supported transitions in ValidateStatusTransition.

Example fix

// before
workflowExecutionContext.TransitionTo(WorkflowSubStatus.Running);
// after
if (workflowExecutionContext.CanTransitionTo(WorkflowSubStatus.Running))
    workflowExecutionContext.TransitionTo(WorkflowSubStatus.Running);
Defensive patterns

Strategy: validation

Validate before calling

if (!workflowExecutionContext.CanTransitionTo(WorkflowSubStatus.Running))
    throw new InvalidOperationException($"Workflow in {workflowExecutionContext.SubStatus} cannot transition to Running");

Type guard

bool canTransition = context.CanTransitionTo(targetSubStatus);

Try / catch

try { context.TransitionTo(target); } catch (Exception ex) { logger.LogWarning(ex, "Illegal sub-status transition to {Target} from {Current}", target, context.SubStatus); }

Prevention

When it happens

Trigger: Calling WorkflowExecutionContext.TransitionTo with a target sub-status that is not a permitted successor of the current SubStatus (e.g. attempting to move a Finished workflow back to Running, or double-completing an already-Cancelled workflow), typically from custom host code or an activity that manipulates workflow status directly.

Common situations: Custom runners or middleware that resume a completed workflow; race conditions where two threads complete the same workflow instance; hosts upgrading Elsa versions where the transition matrix changed; manually resuming a bookmark after the workflow already finished.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/909db5cf5854ee40. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs:565

    /// Updates a custom property with the specified key on the <see cref="Properties"/> dictionary.
    public T UpdateProperty<T>(string key, Func<T?, T> updater)
    {
        var value = GetProperty<T?>(key);
        value = updater(value);
        Properties[key] = value!;
        return value;
    }

    /// Returns true if the <see cref="Properties"/> dictionary contains the specified key.
    public bool HasProperty(string name) => Properties.ContainsKey(name);

    internal bool CanTransitionTo(WorkflowSubStatus targetSubStatus) => ValidateStatusTransition();

    internal void TransitionTo(WorkflowSubStatus subStatus)
    {
        if (!ValidateStatusTransition())
            throw new($"Cannot transition from {SubStatus} to {subStatus}");

        SubStatus = subStatus;
        UpdatedAt = SystemClock.UtcNow;

        if (Status == WorkflowStatus.Finished)
            FinishedAt = UpdatedAt;

        if (Status == WorkflowStatus.Finished || SubStatus == WorkflowSubStatus.Suspended)
        {
            foreach (var registration in _cancellationRegistrations)
                registration.Dispose();
        }
    }

    /// Creates a new <see cref="ActivityExecutionContext"/> for the specified activity.
    public async Task<ActivityExecutionContext> CreateActivityExecutionContextAsync(IActivity activity, ActivityInvocationOptions? options = null)
    {
        var activityDescriptor = await ActivityRegistryLookup.FindAsync(activity) ?? throw new ActivityNotFoundException(activity.Type);

View on GitHub (pinned to fe9217bdfa)