elsa-workflows/elsa-core · error · ArgumentException

User Task action keys and labels are required.

Error message

User Task action keys and labels are required.

What it means

UserTask.Normalize requires every action in the Actions collection to have both a non-whitespace Key and a non-whitespace Label. If any action fails this check, Normalize throws ArgumentException pointing at the Actions parameter. This guarantees persisted tasks always render usable action buttons.

Solutions

  1. Ensure every UserTaskAction has both Key and Label set before calling Normalize.
  2. Filter out incomplete actions instead of passing them through.
  3. Validate the incoming DTO action list at the API boundary.

Example fix

// before
task.Actions.Add(new UserTaskAction("Approve", null)); // label missing
task.Normalize(); // throws
// after
task.Actions.Add(new UserTaskAction("Approve", "Approve"));
task.Normalize();
Defensive patterns

Strategy: validation

Validate before calling

var invalid = task.Actions.Where(a => string.IsNullOrWhiteSpace(a.Key) || string.IsNullOrWhiteSpace(a.Label)).ToList();
if (invalid.Count > 0)
    return Results.ValidationProblem(new { actions = new[] { "All action keys and labels are required." } });

Type guard

bool HasValidActions(UserTask t) =>
    t.Actions.All(a => !string.IsNullOrWhiteSpace(a.Key) && !string.IsNullOrWhiteSpace(a.Label));

Try / catch

try
{
    task.Normalize();
}
catch (ArgumentException ex) when (ex.ParamName == nameof(UserTask.Actions))
{
    return Results.ValidationProblem(new { actions = new[] { ex.Message } });
}

Prevention

When it happens

Trigger: Calling Normalize (directly or via create/save) with an Actions list containing a UserTaskAction whose Key or Label is null, empty, or whitespace.

Common situations: Dynamically generated action lists where some entries lack labels; API payloads with incomplete action objects; bulk-imported task definitions with placeholder actions.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.UserTasks/Models/UserTaskModels.cs:209

    public DateTimeOffset? DueAt { get; init; }
    public string? Instructions { get; init; }
    public JsonElement? TaskData { get; init; }
    public UserTaskFormReference? FormReference { get; init; }
    public IReadOnlyCollection<UserTaskAction> Actions { get; init; } = [];
    public IReadOnlyCollection<UserTaskInvitationDefinition> Invitations { get; init; } = [];
    public bool EnableTimeoutOutcome { get; init; }
    public bool EnableCancellationOutcome { get; init; }

    public UserTaskDefinitionSnapshot Normalize()
    {
        var actions = Actions.Count == 0
            ? [new UserTaskAction("Complete", "Complete")]
            : Actions;

        if (string.IsNullOrWhiteSpace(Title))
            throw new ArgumentException("A User Task title is required.", nameof(Title));
        if (actions.Any(x => string.IsNullOrWhiteSpace(x.Key) || string.IsNullOrWhiteSpace(x.Label)))
            throw new ArgumentException("User Task action keys and labels are required.", nameof(Actions));
        if (actions.Any(x => string.Equals(x.Key, "Timeout", StringComparison.OrdinalIgnoreCase) || string.Equals(x.Key, "Cancelled", StringComparison.OrdinalIgnoreCase)))
            throw new ArgumentException("Timeout and Cancelled are reserved User Task action keys.");
        if (actions.Select(x => x.Key).Distinct(StringComparer.OrdinalIgnoreCase).Count() != actions.Count)
            throw new ArgumentException("User Task action keys must be unique.");
        if (Priority is < 0 or > 100)
            throw new ArgumentOutOfRangeException(nameof(Priority), "Priority must be between 0 and 100.");
        if (Invitations.Any(x => string.IsNullOrWhiteSpace(x.VerifierName) || x.AllowedActions.Count == 0 || x.AllowedActions.Any(string.IsNullOrWhiteSpace)))
            throw new ArgumentException("Invitation verifier names and allowed actions are required.", nameof(Invitations));
        if (Invitations.Any(invitation => invitation.AllowedActions.Any(allowed => !actions.Any(action => string.Equals(action.Key, allowed, StringComparison.OrdinalIgnoreCase)))))
            throw new ArgumentException("Invitation actions must be configured User Task actions.", nameof(Invitations));

        return this with { Actions = actions };
    }
}

public sealed class UserTask
{
    public string Id { get; set; } = Guid.NewGuid().ToString("N");

View on GitHub (pinned to fe9217bdfa)