{"record":{"id":"e26b542c132c041b","repo":"elsa-workflows/elsa-core","slug":"invitation-actions-must-be-configured-user-task-actions","errorCode":null,"errorMessage":"Invitation actions must be configured User Task actions.","messagePattern":"Invitation actions must be configured User Task actions\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.UserTasks/Models/UserTaskModels.cs","lineNumber":219,"sourceCode":"    {\n        var actions = Actions.Count == 0\n            ? [new UserTaskAction(\"Complete\", \"Complete\")]\n            : Actions;\n\n        if (string.IsNullOrWhiteSpace(Title))\n            throw new ArgumentException(\"A User Task title is required.\", nameof(Title));\n        if (actions.Any(x => string.IsNullOrWhiteSpace(x.Key) || string.IsNullOrWhiteSpace(x.Label)))\n            throw new ArgumentException(\"User Task action keys and labels are required.\", nameof(Actions));\n        if (actions.Any(x => string.Equals(x.Key, \"Timeout\", StringComparison.OrdinalIgnoreCase) || string.Equals(x.Key, \"Cancelled\", StringComparison.OrdinalIgnoreCase)))\n            throw new ArgumentException(\"Timeout and Cancelled are reserved User Task action keys.\");\n        if (actions.Select(x => x.Key).Distinct(StringComparer.OrdinalIgnoreCase).Count() != actions.Count)\n            throw new ArgumentException(\"User Task action keys must be unique.\");\n        if (Priority is < 0 or > 100)\n            throw new ArgumentOutOfRangeException(nameof(Priority), \"Priority must be between 0 and 100.\");\n        if (Invitations.Any(x => string.IsNullOrWhiteSpace(x.VerifierName) || x.AllowedActions.Count == 0 || x.AllowedActions.Any(string.IsNullOrWhiteSpace)))\n            throw new ArgumentException(\"Invitation verifier names and allowed actions are required.\", nameof(Invitations));\n        if (Invitations.Any(invitation => invitation.AllowedActions.Any(allowed => !actions.Any(action => string.Equals(action.Key, allowed, StringComparison.OrdinalIgnoreCase)))))\n            throw new ArgumentException(\"Invitation actions must be configured User Task actions.\", nameof(Invitations));\n\n        return this with { Actions = actions };\n    }\n}\n\npublic sealed class UserTask\n{\n    public string Id { get; set; } = Guid.NewGuid().ToString(\"N\");\n    public string TenantId { get; set; } = \"\";\n    public string WorkflowDefinitionId { get; set; } = \"\";\n    public string? WorkflowDefinitionName { get; set; }\n    public int? WorkflowDefinitionVersion { get; set; }\n    public string WorkflowInstanceId { get; set; } = \"\";\n    /// <summary>A safe, host-authored instance reference (correlation ID or instance name). Never a bookmark or token.</summary>\n    public string? WorkflowInstanceReference { get; set; }\n    public string ActivityInstanceId { get; set; } = \"\";\n    public string BookmarkId { get; set; } = \"\";\n    public string MaterializationKey { get; set; } = \"\";","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.UserTasks/Models/UserTaskModels.cs#L201-L237","documentation":"Normalize() cross-checks that every AllowedActions entry on each invitation references an action key that exists in the definition's Actions list (case-insensitive). If an invitation names an action that is not configured, the ArgumentException is thrown. This keeps invitations from granting actions the task cannot deliver.","triggerScenarios":"Renaming or deleting a User Task action (e.g. 'Rejected' → 'Reject') without updating Invitations' AllowedActions; hand-authored definitions where invitations were copied from another task; typo in an allowed action key.","commonSituations":"Definition versioning/migration where action sets changed; copy-paste between task definitions; spelling differences like 'approve ' with a trailing space.","solutions":["Update each invitation's AllowedActions to reference existing action keys after changing Actions.","Validate and fix definitions after any rename/migration of action keys.","Trim and normalize action keys when authoring to avoid whitespace/case mismatches."],"exampleFix":"// before\nActions = [new() { Key = \"Reject\", Label = \"Reject\" }],\nInvitations = [new() { VerifierName = \"Managers\", AllowedActions = [\"Rejected\"] }]\n// after\nInvitations = [new() { VerifierName = \"Managers\", AllowedActions = [\"Reject\"] }]","handlingStrategy":"validation","validationCode":"var keys = definition.Actions.Select(a => a.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);\nvar dangling = definition.Invitations.SelectMany(i => i.AllowedActions).Where(a => !keys.Contains(a)).ToList();\nif (dangling.Count > 0) throw new InvalidOperationException($\"Unknown action keys: {string.Join(\", \", dangling)}\");","typeGuard":null,"tryCatchPattern":"try { definition = definition.Normalize(); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"configured User Task actions\")) { /* re-map invitation actions */ }","preventionTips":["Treat invitation AllowedActions as foreign keys and update them in the same change as action renames","Run Normalize() as part of your definition CI/linting","Provide UI dropdowns populated from the actual action list instead of free-text"],"tags":["validation","user-task","referential-integrity"],"backgroundTag":"invalid-argument-value","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}