elsa-workflows/elsa-core · error · InvalidOperationException

Outbox item does not contain a dispatch command for kind .

Error message

Outbox item {item.Id} does not contain a dispatch command for kind {item.Kind}.

What it means

WorkflowDispatchOutboxProcessor.SendAsync switches on the outbox item's Kind and sends the corresponding dispatch command. When the item's kind-specific command payload (TriggerWorkflowsCommand or ResumeWorkflowsCommand) is null, the default case throws InvalidOperationException stating the item does not contain a dispatch command for its kind. This is a data-integrity guard against corrupt/partially written outbox items.

Solutions

  1. Inspect the offending outbox item record and delete or repair it (it cannot be dispatched without its command payload).
  2. Verify outbox item serialization/deserialization matches across writer and processor versions (schema alignment after upgrades).
  3. Fix the producer code path so it always assigns the command matching the Kind before saving the item.
  4. Catch the InvalidOperationException in ProcessAsync and route the item to a dead-letter/failure state instead of stalling the processor loop.

Example fix

// before
var item = new WorkflowDispatchOutboxItem
{
    Kind = WorkflowDispatchOutboxItemKind.TriggerWorkflows
    // TriggerWorkflowsCommand never set
};

// after
var item = new WorkflowDispatchOutboxItem
{
    Kind = WorkflowDispatchOutboxItemKind.TriggerWorkflows,
    TriggerWorkflowsCommand = triggerCommand
};
if (item.TriggerWorkflowsCommand == null && item.ResumeWorkflowsCommand == null)
    throw new InvalidOperationException("Outbox item created without a dispatch command.");
Defensive patterns

Strategy: try-catch

Validate before calling

if (item.TriggerWorkflowsCommand == null && item.ResumeWorkflowsCommand == null)
    logger.LogError("Outbox item {Id} has no dispatch command payload; routing to dead-letter.", item.Id);

Type guard

bool HasDispatchCommand(WorkflowDispatchOutboxItem item) =>
    (item.Kind == WorkflowDispatchOutboxItemKind.TriggerWorkflows && item.TriggerWorkflowsCommand != null) ||
    (item.Kind == WorkflowDispatchOutboxItemKind.ResumeWorkflows && item.ResumeWorkflowsCommand != null);

Try / catch

try
{
    await SendAsync(item, headers, ct);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("does not contain a dispatch command"))
{
    await MarkItemFailedAsync(item, ex, ct);
}

Prevention

When it happens

Trigger: Processing an outbox item whose Kind says TriggerWorkflows or ResumeWorkflows but whose corresponding command property is null — typically due to a serialization/write failure when the item was saved, or a bug constructing the item.

Common situations: Upgrade/migration changing the outbox item schema so payloads fail to deserialize; partial writes to the outbox store; manually inserted or test-fabricated items missing payloads; message-envelope deserialization dropping unknown fields.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.Workflows.Runtime/Services/WorkflowDispatchOutboxProcessor.cs:168

    {
        var headers = TenantHeaders.CreateHeaders(item.TenantId);

        switch (item.Kind)
        {
            case WorkflowDispatchOutboxItemKind.WorkflowDefinition when item.WorkflowDefinitionCommand != null:
                await commandSender.SendAsync(item.WorkflowDefinitionCommand, CommandStrategy.Background, headers, cancellationToken);
                break;
            case WorkflowDispatchOutboxItemKind.WorkflowInstance when item.WorkflowInstanceCommand != null:
                await commandSender.SendAsync(item.WorkflowInstanceCommand, CommandStrategy.Background, headers, cancellationToken);
                break;
            case WorkflowDispatchOutboxItemKind.TriggerWorkflows when item.TriggerWorkflowsCommand != null:
                await commandSender.SendAsync(item.TriggerWorkflowsCommand, CommandStrategy.Background, headers, cancellationToken);
                break;
            case WorkflowDispatchOutboxItemKind.ResumeWorkflows when item.ResumeWorkflowsCommand != null:
                await commandSender.SendAsync(item.ResumeWorkflowsCommand, CommandStrategy.Background, headers, cancellationToken);
                break;
            default:
                throw new InvalidOperationException($"Outbox item {item.Id} does not contain a dispatch command for kind {item.Kind}.");
        }

        logger.LogDebug("Delivered workflow dispatch outbox item {OutboxItemId} for owner workflow {WorkflowInstanceId}", item.Id, item.OwnerWorkflowInstanceId);
    }

    private async Task HandleMissingOwnerAsync(WorkflowDispatchOutboxItem item, CancellationToken cancellationToken)
    {
        var retention = dispatcherOptions.Value.OrphanedOutboxItemRetention;
        var expiresAt = item.CreatedAt.Add(retention);

        if (retention <= TimeSpan.Zero || systemClock.UtcNow >= expiresAt)
        {
            logger.LogWarning("Deleting workflow dispatch outbox item {OutboxItemId} because owner workflow {WorkflowInstanceId} was not found and the orphan retention period has elapsed", item.Id, item.OwnerWorkflowInstanceId);
            await TryDeleteRetainedItemAsync(item, cancellationToken);
            return;
        }

        logger.LogDebug("Skipping workflow dispatch outbox item {OutboxItemId} because owner workflow {WorkflowInstanceId} was not found; it will be retained until {ExpiresAt}", item.Id, item.OwnerWorkflowInstanceId, expiresAt);

View on GitHub (pinned to fe9217bdfa)