elsa-workflows/elsa-core · error · ArgumentException
A proposal creator is required.
Error message
A proposal creator is required.
What it means
EFCoreAIProposalStore.Validate rejects an AIProposal that is missing required identity fields before writing it to the database. In this case proposal.CreatedBy is null or whitespace, meaning the proposal does not record who created it, which the store treats as a mandatory audit field. The exception is an ArgumentException naming the proposal parameter, thrown from Validate during SaveAsync.
Solutions
- Set proposal.CreatedBy to the acting user/service identity before calling SaveAsync.
- If the creator is unknown, use an explicit sentinel such as "system" rather than leaving it empty.
- Add upstream validation at the endpoint/handler level so unassigned proposals are rejected with a clear message before reaching the store.
Example fix
// before
var proposal = new AIProposal { Id = id, ConversationId = conversationId, Payload = payload };
await store.SaveAsync(proposal, ct);
// after
var proposal = new AIProposal { Id = id, ConversationId = conversationId, Payload = payload, CreatedBy = currentUser.Id };
await store.SaveAsync(proposal, ct); Defensive patterns
Strategy: validation
Validate before calling
if (proposal is null) throw new ArgumentNullException(nameof(proposal));
if (string.IsNullOrWhiteSpace(proposal.Id)) throw new ArgumentException("A proposal ID is required.", nameof(proposal));
if (string.IsNullOrWhiteSpace(proposal.ConversationId)) throw new ArgumentException("A proposal conversation ID is required.", nameof(proposal));
if (string.IsNullOrWhiteSpace(proposal.CreatedBy)) throw new ArgumentException("A proposal creator is required.", nameof(proposal)); Type guard
bool HasCreator(Elsa.AI.Models.AIProposal p) => !string.IsNullOrWhiteSpace(p?.CreatedBy);
Try / catch
try
{
await store.SaveAsync(proposal, ct);
}
catch (ArgumentException ex) when (ex.ParamName == "proposal")
{
logger.LogError(ex, "Invalid AI proposal: {Message}", ex.Message);
} Prevention
- Always populate CreatedBy from the authenticated principal or an explicit 'system' identity when constructing proposals.
- Centralize proposal construction in a factory that enforces required fields.
- Validate proposals at API boundaries before they reach persistence code.
When it happens
Trigger: Calling EFCoreAIProposalStore.SaveAsync(proposal, ct) where proposal.CreatedBy is null, empty, or whitespace, while proposal.Id and proposal.ConversationId are set.
Common situations: Constructing an AIProposal manually in code or tests without assigning CreatedBy; mapping from an API/DTO that omits the creator field; code paths that copy a proposal but drop the creator during transformation.
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
- A proposal ID is required.
- A proposal conversation ID is required.
- A conversation ID is required. (Parameter 'conversation')
- A conversation user ID is required. (Parameter…
- Cannot overwrite an AI proposal that belongs to another…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/3e0385a1d9fafd20.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs:138
private static string NormalizeTenantId(string? tenantId) => tenantId ?? "";
private static void ValidateUserOwnership(AIProposalRecord record, AIProposal proposal)
{
if (!string.IsNullOrWhiteSpace(record.CreatedBy) && !string.Equals(record.CreatedBy, proposal.CreatedBy, StringComparison.Ordinal))
throw new InvalidOperationException("Cannot overwrite an AI proposal that belongs to another user.");
}
private static void Validate(AIProposal proposal)
{
if (string.IsNullOrWhiteSpace(proposal.Id))
throw new ArgumentException("A proposal ID is required.", nameof(proposal));
if (string.IsNullOrWhiteSpace(proposal.ConversationId))
throw new ArgumentException("A proposal conversation ID is required.", nameof(proposal));
if (string.IsNullOrWhiteSpace(proposal.CreatedBy))
throw new ArgumentException("A proposal creator is required.", nameof(proposal));
}
}
View on GitHub (pinned to fe9217bdfa)