elsa-workflows/elsa-core · error · DocumentStoreValidationException
Stored User Task document
Error message
Stored User Task document '{document.Id}' could not be deserialized. What it means
Deserialize converts a stored document's JSON content back into a UserTask. If System.Text.Json returns null (e.g., the content deserializes to a null value rather than a valid UserTask object), the code throws DocumentStoreValidationException, indicating the stored document is corrupt or not the expected schema.
Solutions
- Inspect and repair or delete the corrupt document in the store.
- Re-create the user task via the normal create flow.
- Verify the serializer options used at write time match the reader (same JsonOptions/version).
Example fix
// before
var task = await repo.GetAsync(taskId, ct); // throws DocumentStoreValidationException
// after
try {
var task = await repo.GetAsync(taskId, ct);
}
catch (DocumentStoreValidationException ex) {
logger.LogError(ex, "Corrupt user task document {TaskId}; recreating.", taskId);
await repo.DeleteAsync(taskId, ct);
task = await repo.CreateAsync(newTask, ct);
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate stored content before deserializing yourself
if (string.IsNullOrWhiteSpace(document.Content) || document.Content.Trim() == "null")
logger.LogError("User task document {DocumentId} has null/empty content.", document.Id); Try / catch
try
{
var task = await repo.GetAsync(taskId, ct);
}
catch (DocumentStoreValidationException ex)
{
logger.LogError(ex, "Stored document for {TaskId} is corrupt.", taskId);
// fall back to delete-and-recreate or surface an admin error
} Prevention
- Never write null/empty content; always serialize a full UserTask.
- Keep serializer options consistent between writes and reads across versions.
- Add a store health check that round-trips a sample document.
When it happens
Trigger: Calling GetAsync or any LoadDocumentAsync-backed path on a document whose Content is 'null', empty, or deserializes to null under the repository's JsonOptions.
Common situations: Documents written by an older/other schema version; manual edits to the store; write failures that persisted null content; ID/tenant mapping bugs loading a foreign document.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- Stored secret document
- A User Task title is required.
- Cannot convert to bool
- Cannot convert to bool?
- Cannot deserialize to .
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/f7e5ab016dcd1c80.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.UserTasks.Persistence.VNext/Repositories/VNextUserTaskRepository.cs:214
["TaskType"] = task.TaskType,
// Every index the schema provider declares must be supplied on save; the store rejects the
// write outright when one is absent, so an omission here disables the provider entirely
// rather than merely losing an index.
["WorkflowDefinitionId"] = task.WorkflowDefinitionId,
["WorkflowInstanceId"] = task.WorkflowInstanceId,
["ActivityInstanceId"] = task.ActivityInstanceId,
["CreatedAt"] = task.CreatedAt.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
["CompletedAt"] = task.CompletedAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
["AssigneeProvider"] = task.Assignee?.Provider,
["AssigneeType"] = task.Assignee?.Type.ToString(),
["AssigneeId"] = task.Assignee?.Id,
["HealthSeverity"] = task.HealthSeverity?.ToString(),
["Priority"] = task.Priority.ToString(System.Globalization.CultureInfo.InvariantCulture),
["DueAt"] = task.DueAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture)
}, expectedVersion);
private static UserTask Deserialize(StoredDocument document) => JsonSerializer.Deserialize<UserTask>(document.Content, JsonOptions)
?? throw new DocumentStoreValidationException($"Stored User Task document '{document.Id}' could not be deserialized.");
private static string DocumentId(string tenantId, string taskId) => $"{tenantId}:{taskId}";
private static bool Matches(UserTask task, UserTaskQuery query)
{
var search = query.Search?.Trim();
return task.TenantId == query.TenantId && (query.Statuses.Count == 0 || query.Statuses.Contains(task.Status)) &&
(!query.OnlyOverdue || task.IsOverdue) && (!query.OnlyWithoutDueDate || task.DueAt is null) &&
(string.IsNullOrWhiteSpace(query.TaskType) || task.TaskType == query.TaskType) &&
(!query.PriorityFrom.HasValue || task.Priority >= query.PriorityFrom) && (!query.PriorityTo.HasValue || task.Priority <= query.PriorityTo) &&
(!query.DueFrom.HasValue || task.DueAt >= query.DueFrom) && (!query.DueTo.HasValue || task.DueAt <= query.DueTo) &&
(string.IsNullOrWhiteSpace(query.WorkflowDefinitionId) || task.WorkflowDefinitionId == query.WorkflowDefinitionId) &&
(string.IsNullOrWhiteSpace(query.WorkflowInstanceId) || task.WorkflowInstanceId == query.WorkflowInstanceId) &&
(string.IsNullOrWhiteSpace(query.Reference) || task.Reference == query.Reference) &&
(string.IsNullOrWhiteSpace(search) || task.Title.Contains(search, StringComparison.OrdinalIgnoreCase) || task.Summary?.Contains(search, StringComparison.OrdinalIgnoreCase) == true || task.Reference?.Contains(search, StringComparison.OrdinalIgnoreCase) == true || task.TaskType?.Contains(search, StringComparison.OrdinalIgnoreCase) == true);
}
private static bool IsVisible(UserTask task, UserTaskQueryScope scope)View on GitHub (pinned to fe9217bdfa)