{"record":{"id":"d21482507f588b34","repo":"ppy/osu","slug":"attempted-to-remove-an-item-which-is-not-owned-by","errorCode":null,"errorMessage":"Attempted to remove an item which is not owned by the user.","messagePattern":"Attempted to remove an item which is not owned by the user\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs","lineNumber":599,"sourceCode":"        }\r\n\r\n        public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(item));\r\n\r\n        public async Task RemoveUserPlaylistItem(int userId, long playlistItemId)\r\n        {\r\n            Debug.Assert(ServerRoom != null);\r\n            Debug.Assert(ServerAPIRoom != null);\r\n\r\n            var item = ServerRoom.Playlist.FirstOrDefault(i => i.ID == playlistItemId);\r\n\r\n            if (item == null)\r\n                throw new InvalidOperationException(\"Item does not exist in the room.\");\r\n\r\n            if (item.Equals(currentItem))\r\n                throw new InvalidOperationException(\"The room's current item cannot be removed.\");\r\n\r\n            if (item.OwnerID != userId)\r\n                throw new InvalidOperationException(\"Attempted to remove an item which is not owned by the user.\");\r\n\r\n            if (item.Expired)\r\n                throw new InvalidOperationException(\"Attempted to remove an item which has already been played.\");\r\n\r\n            ServerRoom.Playlist.Remove(item);\r\n            ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Where(i => i.ID != item.ID).ToArray();\r\n            await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false);\r\n\r\n            await updateCurrentItem(ServerRoom).ConfigureAwait(false);\r\n            updateRoomStateIfRequired();\r\n        }\r\n\r\n        public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(playlistItemId));\r\n\r\n        public override Task VoteToSkipIntro()\r\n        {\r\n            return UserVoteToSkipIntro(api.LocalUser.Value.OnlineID);\r\n        }\r","sourceCodeStart":581,"sourceCodeEnd":617,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs#L581-L617","documentation":"RemoveUserPlaylistItem enforces ownership: only the item's owner may remove it (there is no host carve-out in the remove path, unlike edit). Removing an item whose OwnerID differs from the acting userId throws.","triggerScenarios":"Calling RemoveUserPlaylistItem/RemovePlaylistItem as a user whose id != item.OwnerID.","commonSituations":"Test removes an item as a non-owner; assuming host can remove others' items (the remove path does not grant that).","solutions":["Remove the item as its owner (call with userId == item.OwnerID).","If a different user must trigger removal, have that user own the item in the first place."],"exampleFix":"// before\nawait client.RemoveUserPlaylistItem(nonOwnerUserId, itemId);\n\n// after\nawait client.RemoveUserPlaylistItem(item.OwnerID, itemId);","handlingStrategy":"validation","validationCode":"var item = ServerRoom.Playlist.FirstOrDefault(i => i.ID == playlistItemId);\nif (item != null && item.OwnerID != userId)\n    throw new InvalidOperationException(\"only the owner may remove\");\nawait client.RemoveUserPlaylistItem(userId, playlistItemId);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember the remove path has no host carve-out — only the owner may remove.","Remove items as the user who added them.","Do not assume host privileges extend to removal."],"tags":["test-infra","multiplayer","authorization","ownership"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}