ppy/osu · error · InvalidOperationException

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

Error message

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

What it means

EditUserPlaylistItem requires the editor be the item's owner OR the room host; editing another user's item without host privileges throws. This mirrors the server's ownership authorization on playlist edits.

Source

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

        }

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

        public async Task EditUserPlaylistItem(int userId, MultiplayerPlaylistItem item)
        {
            Debug.Assert(ServerRoom != null);
            Debug.Assert(currentItem != null);
            Debug.Assert(ServerAPIRoom != null);

            item.OwnerID = userId;

            var existingItem = ServerRoom.Playlist.SingleOrDefault(i => i.ID == item.ID);

            if (existingItem == null)
                throw new InvalidOperationException("Attempted to change an item that doesn't exist.");

            if (existingItem.OwnerID != userId && ServerRoom.Host?.UserID != LocalUser?.UserID)
                throw new InvalidOperationException("Attempted to change an item which is not owned by the user.");

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

            // Ensure the playlist order doesn't change.
            item.PlaylistOrder = existingItem.PlaylistOrder;

            ServerRoom.Playlist[ServerRoom.Playlist.IndexOf(existingItem)] = item;
            ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : ServerAPIRoom.Playlist[i]).ToArray();

            await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false);
        }

        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);

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Edit the item as the user who owns it (item.OwnerID).
  2. Act as the room host (TransferHost) before editing another user's item.
  3. Re-create the item under the acting user if ownership transfer is not intended.

Example fix

// before
await client.EditUserPlaylistItem(nonOwnerUserId, item);

// after
await client.EditUserPlaylistItem(item.OwnerID, item);
// or make the actor host first:
client.TransferHost(actorUserId);
await client.EditUserPlaylistItem(actorUserId, item);
Defensive patterns

Strategy: validation

Validate before calling

var existing = ServerRoom.Playlist.Single(i => i.ID == item.ID);
if (existing.OwnerID != userId && ServerRoom.Host?.UserID != userId)
    throw new InvalidOperationException("not owner and not host");
await client.EditUserPlaylistItem(userId, item);

Prevention

When it happens

Trigger: Editing an item whose OwnerID differs from the acting userId while ServerRoom.Host?.UserID is also not the acting user.

Common situations: Test edits as a different user than the one who added the item; the host carve-out was assumed but the acting user is not host.

Related errors


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