{"record":{"id":"2cf0d37294ebbc64","repo":"ppy/osu","slug":"attempted-to-remove-an-item-which-has-already-been","errorCode":null,"errorMessage":"Attempted to remove an item which has already been played.","messagePattern":"Attempted to remove an item which has already been played\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs","lineNumber":602,"sourceCode":"\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\n\r\n        public async Task UserVoteToSkipIntro(int userId)\r\n        {\r","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs#L584-L620","documentation":"RemoveUserPlaylistItem refuses to remove items whose Expired flag is true. Once an item has been played it is considered historical and cannot be removed through the normal removal path.","triggerScenarios":"Removing a playlist item that has already been played (item.Expired == true).","commonSituations":"Test advanced past an item then tried to clean it up; removing a stale reference to a played item.","solutions":["Only attempt to remove non-expired items (item.Expired == false).","Filter expired items out before issuing removal.","Accept that expired items remain in history; do not treat them as removable."],"exampleFix":"// before\nawait client.RemovePlaylistItem(expiredItem.ID);\n\n// after\nvar removable = ServerRoom.Playlist.FirstOrDefault(i => !i.Expired && i.ID != currentItem?.ID);\nif (removable != null) await client.RemovePlaylistItem(removable.ID);","handlingStrategy":"validation","validationCode":"var item = ServerRoom.Playlist.FirstOrDefault(i => i.ID == playlistItemId);\nif (item != null && item.Expired)\n    throw new InvalidOperationException(\"item already played; not removable\");\nawait client.RemovePlaylistItem(playlistItemId);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Filter removal candidates to non-expired items.","Do not attempt to clean up historical/expired items via removal.","Re-query item.Expired before each remove in long-running tests."],"tags":["test-infra","multiplayer","playlist","lifecycle"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}