{"record":{"id":"270d77873f06ab6f","repo":"elsa-workflows/elsa-core","slug":"cannot-overwrite-an-ai-conversation-that-belongs-to-another-270d77","errorCode":null,"errorMessage":"Cannot overwrite an AI conversation that belongs to another user.","messagePattern":"Cannot overwrite an AI conversation that belongs to another user\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.AI.Host/Services/InMemoryAIConversationStore.cs","lineNumber":63,"sourceCode":"    private bool IsExpired(AIConversation conversation)\n    {\n        if (conversation.RetentionMode == AIRetentionMode.Ephemeral)\n            return conversation.Status is AIConversationStatus.Completed or AIConversationStatus.Failed;\n\n        if (conversation.RetentionMode == AIRetentionMode.Durable)\n            return false;\n\n        var expiresAt = conversation.RetentionExpiresAt;\n        return expiresAt.HasValue && expiresAt <= DateTimeOffset.UtcNow;\n    }\n\n    private static void ValidateOwnership(AIConversation existing, AIConversation conversation)\n    {\n        if (!string.Equals(NormalizeTenantId(existing.TenantId), NormalizeTenantId(conversation.TenantId), StringComparison.Ordinal))\n            throw new InvalidOperationException(\"Cannot overwrite an AI conversation that belongs to another tenant.\");\n\n        if (!string.IsNullOrWhiteSpace(existing.UserId) && !string.Equals(existing.UserId, conversation.UserId, StringComparison.Ordinal))\n            throw new InvalidOperationException(\"Cannot overwrite an AI conversation that belongs to another user.\");\n    }\n\n    private static string NormalizeTenantId(string? tenantId) => tenantId ?? \"\";\n\n    private static void Validate(AIConversation conversation)\n    {\n        if (string.IsNullOrWhiteSpace(conversation.Id))\n            throw new ArgumentException(\"A conversation ID is required.\", nameof(conversation));\n\n        if (string.IsNullOrWhiteSpace(conversation.UserId))\n            throw new ArgumentException(\"A conversation user ID is required.\", nameof(conversation));\n    }\n}\n","sourceCodeStart":45,"sourceCodeEnd":77,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.AI.Host/Services/InMemoryAIConversationStore.cs#L45-L77","documentation":"InMemoryAIConversationStore.SaveAsync rejects overwriting an existing conversation whose UserId differs from the incoming one, when the stored record has a non-empty UserId. This prevents one user from replacing another user's conversation. Thrown from ValidateOwnership during SaveAsync.","triggerScenarios":"Calling SaveAsync with a conversation whose Id matches an existing record but whose UserId differs from the stored UserId.","commonSituations":"Switching authenticated users in tests without clearing the store; user ID mismatches after auth refactors; replaying conversation saves under a different identity.","solutions":["Ensure the same UserId is supplied when updating an existing conversation.","Use a fresh conversation ID for a different user.","Delete the existing conversation first if reassignment is intended.","Check that the current-user provider returns a stable UserId across calls."],"exampleFix":"// before\nawait store.SaveAsync(new AIConversation { Id = id, TenantId = tenant, UserId = \"user-b\" });\n// after\nawait store.SaveAsync(new AIConversation { Id = id, TenantId = tenant, UserId = \"user-a\" });","handlingStrategy":"validation","validationCode":"var existing = await store.FindAsync(conversation.Id);\nif (existing is not null && !string.IsNullOrWhiteSpace(existing.UserId) && existing.UserId != conversation.UserId)\n    throw new InvalidOperationException(\"User mismatch for conversation \" + conversation.Id);","typeGuard":"bool SameUser(AIConversation? existing, AIConversation c) => existing is null || string.IsNullOrWhiteSpace(existing.UserId) || existing.UserId == c.UserId;","tryCatchPattern":"try { await store.SaveAsync(conversation); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"another user\")) { logger.LogWarning(ex, \"Cross-user save blocked for {Id}\", conversation.Id); }","preventionTips":["Load the existing conversation and reuse its UserId on updates.","Keep the authenticated user ID stable for the conversation lifetime.","Test user-switch scenarios against the store."],"tags":["dotnet","authorization","in-memory-store","multi-tenancy"],"backgroundTag":"permission-denied","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"}