elsa-workflows/elsa-core · error · InvalidOperationException
Cannot overwrite an AI proposal that belongs to another…
Error message
Cannot overwrite an AI proposal that belongs to another user.
What it means
ValidateUserOwnership for proposals: when overwriting an existing proposal record that has a non-empty CreatedBy, the store requires the incoming proposal.CreatedBy to match exactly (ordinal). Mismatch throws InvalidOperationException to stop one creator overwriting another user's proposal.
Solutions
- Verify proposal.CreatedBy is populated with the same user who originally created the proposal.
- Use fresh IDs for each user's proposals instead of shared/deterministic IDs.
- Fetch the record first and only allow updates from the owning creator's context; otherwise create a new proposal.
- For admin flows, delete and recreate rather than overwriting another creator's record.
Example fix
// before proposal.CreatedBy = null; // identity lost in service layer await proposalStore.SaveAsync(proposal); // after proposal.CreatedBy = originalCreatedBy ?? currentUser.Id; await proposalStore.SaveAsync(proposal);
Defensive patterns
Strategy: validation
Validate before calling
if (!string.IsNullOrWhiteSpace(proposal.Id) && string.IsNullOrWhiteSpace(proposal.CreatedBy)) throw new ArgumentException("CreatedBy is required and must match the record creator."); Type guard
static bool HasCreator(AIProposal p) => !string.IsNullOrWhiteSpace(p.CreatedBy);
Try / catch
try { await proposalStore.SaveAsync(proposal); }
catch (InvalidOperationException ex) when (ex.Message.Contains("belongs to another user")) { /* reject update; require a fresh proposal for this creator */ } Prevention
- Always populate CreatedBy from the authenticated user.
- Fetch-and-verify ownership before updating existing proposals.
- Never share proposal IDs across creators.
- Cover ownership rules in unit tests.
When it happens
Trigger: Calling SaveAsync (or the retry path) on an existing proposal where record.CreatedBy is non-whitespace and record.CreatedBy != proposal.CreatedBy.
Common situations: Reusing a proposal Id from another user's session; losing the authenticated user identity between create and update (CreatedBy empty or different); mapping bugs copying the wrong user field; importing proposals between users.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Cannot overwrite an AI conversation that belongs to another…
- Cannot overwrite an AI proposal that belongs to another…
- Failed to insert AI proposal
- A proposal ID is required.
- A proposal conversation ID is required.
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/f1e21183d84a1786.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs:126
record.ReviewedBy = proposal.ReviewedBy;
record.ReviewedAt = proposal.ReviewedAt;
record.AppliedBy = proposal.AppliedBy;
record.AppliedAt = proposal.AppliedAt;
}
private static TEnum ParseEnum<TEnum>(string value, TEnum defaultValue) where TEnum : struct =>
Enum.TryParse<TEnum>(value, ignoreCase: true, out var result) ? result : defaultValue;
private static bool BelongsToTenant(string? storedTenantId, string? requestedTenantId) =>
string.Equals(NormalizeTenantId(storedTenantId), NormalizeTenantId(requestedTenantId), StringComparison.Ordinal);
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)