elsa-workflows/elsa-core · error · InvalidOperationException

Cannot overwrite an AI conversation that belongs to another…

Error message

Cannot overwrite an AI conversation that belongs to another user.

What it means

ValidateUserOwnership guards conversations that already have a UserId: when overwriting an existing record, if the stored record's UserId is non-empty and does not exactly match (ordinal) the UserId on the conversation being saved, the store throws InvalidOperationException. This prevents one user from overwriting another user's AI conversation with the same Id.

Solutions

  1. Verify the AIConversation.UserId is correctly populated before SaveAsync; it must exactly match the record's stored UserId (ordinal).
  2. Generate a fresh conversation Id for each user/session instead of reusing a fixed Id.
  3. Query the existing record first and map updates from the actual owning user's context.
  4. If ownership genuinely changed through an admin path, delete and recreate the conversation rather than overwriting.

Example fix

// before
conversation.UserId = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); // may be null in service context
await store.SaveAsync(conversation);
// after
if (conversation.UserId != storedUserId) conversation = storedConversation with updates; // keep original owner
await store.SaveAsync(conversation);
Defensive patterns

Strategy: validation

Validate before calling

if (!string.IsNullOrWhiteSpace(conversation.Id) && conversation.UserId is null || string.IsNullOrWhiteSpace(conversation.UserId))
    throw new ArgumentException("UserId is required and must match the record owner.");

Type guard

static bool HasOwner(AIConversation c) => !string.IsNullOrWhiteSpace(c.UserId);

Try / catch

try { await store.SaveAsync(conversation); }
catch (InvalidOperationException ex) when (ex.Message.Contains("belongs to another user")) { /* reject the update; do not overwrite the other user's conversation */ }

Prevention

When it happens

Trigger: Calling SaveAsync (directly or through RetryAsUpdateAsync) on an existing conversation record where record.UserId is non-whitespace and record.UserId != conversation.UserId (ordinal comparison).

Common situations: Reusing conversation IDs across user sessions; a deserialization or mapping bug dropping/altering the UserId before save; shared test fixtures reusing the same conversation Id for different users; an anonymous context (UserId empty on the incoming object) attempting to overwrite a user-owned conversation.

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


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/65108f49e664f03f. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIConversationStore.cs:225

        var expiresAt = conversation.RetentionExpiresAt;
        if (expiresAt == null)
            return false;

        return expiresAt <= DateTimeOffset.UtcNow;
    }

    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(AIConversationRecord record, AIConversation conversation)
    {
        if (!string.IsNullOrWhiteSpace(record.UserId) && !string.Equals(record.UserId, conversation.UserId, StringComparison.Ordinal))
            throw new InvalidOperationException("Cannot overwrite an AI conversation that belongs to another user.");
    }

    private static void Validate(AIConversation conversation)
    {
        if (string.IsNullOrWhiteSpace(conversation.Id))
            throw new ArgumentException("A conversation ID is required.", nameof(conversation));

        if (string.IsNullOrWhiteSpace(conversation.UserId))
            throw new ArgumentException("A conversation user ID is required.", nameof(conversation));
    }
}

View on GitHub (pinned to fe9217bdfa)