ppy/osu · error · InvalidOperationException

Attempted to change an item which has already been played.

Error message

Attempted to change an item which has already been played.

What it means

EditUserPlaylistItem refuses to modify items whose Expired flag is true. The server marks a playlist item Expired once it has been played, and expired items are immutable.

Source

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

        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);
            Debug.Assert(ServerAPIRoom != null);

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

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Add a fresh playlist item and edit that instead of an expired one.
  2. Edit the item before it gets played (before updateCurrentItem expires it).
  3. If you need to change a played item, remove-and-readd is not possible for expired items either — add a new item with the desired values.

Example fix

// before (item already played)
await client.EditPlaylistItem(expiredItem);

// after
await client.AddPlaylistItem(new MultiplayerPlaylistItem(newBeatmap));
Defensive patterns

Strategy: validation

Validate before calling

var existing = ServerRoom.Playlist.Single(i => i.ID == item.ID);
if (existing.Expired)
    throw new InvalidOperationException("item already played; add a new one");
await client.EditPlaylistItem(item);

Prevention

When it happens

Trigger: Editing an item that has already been played through (existingItem.Expired == true).

Common situations: Test advanced the playlist past an item (finish/start match) then tried to edit it; editing the currentItem after it expired; reusing a stale item reference.

Related errors


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