elsa-workflows/elsa-core · error · FaultException

AlterationFaultCodes.PlanNotFound

AlterationFaultCodes.PlanNotFound

Error message

Alteration Plan with ID {planId} not found.

What it means

The DispatchAlterationJobs activity finds the alteration plan via IAlterationPlanStore.FindAsync using a filter on the PlanId input and throws a FaultException with code AlterationFaultCodes.PlanNotFound when the lookup returns null. Like CompleteAlterationPlan, this is a workflow fault so callers can catch it and route the workflow into an error path.

Solutions

  1. Verify the PlanId input refers to a persisted alteration plan visible to the current tenant.
  2. Persist the plan before dispatching jobs for it.
  3. Add a workflow fault handler for AlterationFaultCodes.PlanNotFound to handle missing plans.
  4. Confirm the store connection/tenant scope used at runtime matches where the plan was saved.

Example fix

// before
await workflowInvoker.DispatchAlterationJobsAsync(planId); // planId may not exist

// after
var plan = await alterationPlanStore.FindAsync(new AlterationPlanFilter { Id = planId }, ct);
if (plan == null)
    throw new InvalidOperationException($"Cannot dispatch jobs: plan {planId} not found.");
await workflowInvoker.DispatchAlterationJobsAsync(planId);
Defensive patterns

Strategy: try-catch

Validate before calling

var plan = await alterationPlanStore.FindAsync(new AlterationPlanFilter { Id = planId }, ct);
if (plan is null)
    throw new InvalidOperationException($"Plan '{planId}' not found; ensure it is persisted and tenant-visible before dispatching jobs.");

Type guard

bool PlanExists(AlterationPlan? plan) => plan is not null;

Try / catch

try
{
    await workflow.RunAsync(instance, ct);
}
catch (FaultException ex) when (ex.Code == AlterationFaultCodes.PlanNotFound)
{
    logger.LogError("Cannot dispatch alteration jobs: {Message}", ex.Message);
}

Prevention

When it happens

Trigger: Executing DispatchAlterationJobs with a PlanId input for which FindAsync returns no plan (nonexistent ID, deleted plan, wrong tenant scope, or plan not yet persisted).

Common situations: Dispatching jobs for a plan created in a different tenant; race where the plan was deleted after being queued; misspelled or stale plan ID stored in workflow input from an earlier failed run.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.Alterations/Activities/DispatchAlterationJobs.cs:50

    /// <summary>
    /// The ID of the alteration plan.
    /// </summary>
    public Input<string> PlanId { get; set; } = null!;

    /// <inheritdoc />
    protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
    {
        var cancellationToken = context.CancellationToken;
        var planId = context.Get(PlanId)!;
        var alterationPlanStore = context.GetRequiredService<IAlterationPlanStore>();
        var planFilter = new AlterationPlanFilter
        {
            Id = planId
        };
        var plan = await alterationPlanStore.FindAsync(planFilter, cancellationToken);

        if (plan == null)
            throw new FaultException(AlterationFaultCodes.PlanNotFound, AlterationFaultCategories.Alteration, DefaultFaultTypes.System, $"Alteration Plan with ID {planId} not found.");

        // Update status.
        plan.Status = AlterationPlanStatus.Dispatching;
        await alterationPlanStore.SaveAsync(plan, cancellationToken);

        // Find all jobs for the plan and dispatch them.
        var filter = new AlterationJobFilter
        {
            PlanId = plan.Id
        };
        var alterationJobStore = context.GetRequiredService<IAlterationJobStore>();
        var alterationJobIds = await alterationJobStore.FindManyIdsAsync(filter, cancellationToken);

        // Dispatch each job.
        var alterationJobDispatcher = context.GetRequiredService<IAlterationJobDispatcher>();
        foreach (var jobId in alterationJobIds)
            await alterationJobDispatcher.DispatchAsync(jobId, cancellationToken);

View on GitHub (pinned to fe9217bdfa)