ppy/osu · error · InvalidOperationException

Attempted to remove an item which is not owned by the user.

Error message

Attempted to remove an item which is not owned by the user.

What it means

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.

Source

Thrown at osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs:599

        }

        public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(item));

        public async Task RemoveUserPlaylistItem(int userId, long playlistItemId)
        {
            Debug.Assert(ServerRoom != null);
            Debug.Assert(ServerAPIRoom != null);

            var item = ServerRoom.Playlist.FirstOrDefault(i => i.ID == playlistItemId);

            if (item == null)
                throw new InvalidOperationException("Item does not exist in the room.");

            if (item.Equals(currentItem))
                throw new InvalidOperationException("The room's current item cannot be removed.");

            if (item.OwnerID != userId)
                throw new InvalidOperationException("Attempted to remove an item which is not owned by the user.");

            if (item.Expired)
                throw new InvalidOperationException("Attempted to remove an item which has already been played.");

            ServerRoom.Playlist.Remove(item);
            ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Where(i => i.ID != item.ID).ToArray();
            await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false);

            await updateCurrentItem(ServerRoom).ConfigureAwait(false);
            updateRoomStateIfRequired();
        }

        public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(playlistItemId));

        public override Task VoteToSkipIntro()
        {
            return UserVoteToSkipIntro(api.LocalUser.Value.OnlineID);
        }

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Remove the item as its owner (call with userId == item.OwnerID).
  2. If a different user must trigger removal, have that user own the item in the first place.

Example fix

// before
await client.RemoveUserPlaylistItem(nonOwnerUserId, itemId);

// after
await client.RemoveUserPlaylistItem(item.OwnerID, itemId);
Defensive patterns

Strategy: validation

Validate before calling

var item = ServerRoom.Playlist.FirstOrDefault(i => i.ID == playlistItemId);
if (item != null && item.OwnerID != userId)
    throw new InvalidOperationException("only the owner may remove");
await client.RemoveUserPlaylistItem(userId, playlistItemId);

Prevention

When it happens

Trigger: Calling RemoveUserPlaylistItem/RemovePlaylistItem as a user whose id != item.OwnerID.

Common situations: Test removes an item as a non-owner; assuming host can remove others' items (the remove path does not grant that).

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/d21482507f588b34. Report an issue: GitHub.