elsa-workflows/elsa-core · error · ArgumentException
Timeout and Cancelled are reserved User Task action keys.
Error message
Timeout and Cancelled are reserved User Task action keys.
What it means
Normalize() validates a UserTaskDefinition before materialization and rejects any action whose key is the reserved string 'Timeout' or 'Cancelled' (case-insensitive). These keys are reserved because the runtime generates synthetic Timeout and Cancelled actions for every User Task, and a user-defined action with the same key would collide with them. The exception carries no parameter name, indicating a definition-level rule violation.
Solutions
- Rename the action to a non-reserved key, e.g. 'Cancelled' → 'Reject' or 'Cancel-Request', and update any invitation AllowedActions referencing the old key.
- Handle the reserved semantics with built-in timeout/cancellation configuration instead of a custom action.
- Add a pre-check on action keys before constructing/normalizing the definition to surface a friendlier error to end users.
Example fix
// before
new UserTaskDefinition { Actions = [ new() { Key = "Cancelled", Label = "Cancel" } ] }
// after
new UserTaskDefinition { Actions = [ new() { Key = "Reject", Label = "Cancel" } ] } Defensive patterns
Strategy: validation
Validate before calling
var reserved = new[]{"Timeout","Cancelled"};
if (definition.Actions.Any(a => reserved.Contains(a.Key, StringComparer.OrdinalIgnoreCase)))
throw new InvalidOperationException("Action keys 'Timeout'/'Cancelled' are reserved."); Try / catch
try { definition = definition.Normalize(); }
catch (ArgumentException ex) when (ex.Message.Contains("reserved")) { /* surface friendly message */ } Prevention
- Keep a shared constant list of reserved action keys and check it when building task definitions
- Validate definitions in your UI/form layer before they reach the runtime
- Never map UI button labels 1:1 onto action keys without a key-naming convention
When it happens
Trigger: Calling Normalize() (directly or via DefaultUserTaskManager.ProjectAsync) on a UserTaskDefinition whose Actions collection contains an entry with Key equal to 'timeout' or 'cancelled' in any casing — e.g. authoring a definition with actions [{key:'Cancel', label:'Cancel'}] is fine, but {key:'Cancelled'} is not.
Common situations: Workflow designers modeling a custom cancel button or timeout action on a User Task; migrations from older task models where Cancelled was a plain action key; case-insensitive duplicates like 'TIMEOUT'.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- User Task action keys must be unique.
- Invitation verifier names and allowed actions are required.
- Invitation actions must be configured User Task actions.
- User Task protected payload exceeds the configured limit.
- A conversation ID is required. (Parameter 'conversation')
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/72e52a8457b2da66.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.UserTasks/Models/UserTaskModels.cs:211
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");
public string TenantId { get; set; } = "";
public string WorkflowDefinitionId { get; set; } = "";View on GitHub (pinned to fe9217bdfa)