Fix Music Assistant playlist not being removed (#2931)
authorOzGav <gavnosp@hotmail.com>
Tue, 6 Jan 2026 08:10:23 +0000 (18:10 +1000)
committerGitHub <noreply@github.com>
Tue, 6 Jan 2026 08:10:23 +0000 (09:10 +0100)
* Fix playlist recreation issue after removal

When a Music Assistant playlist was removed from the library, the config entry
was correctly removed but the physical playlist file in the playlists/ directory
was not deleted. This orphaned file could cause the playlist to be recreated
during synchronization.

This fix ensures that when a playlist is removed via library_remove(), both the
config entry and the physical playlist file are deleted.

Fixes the issue where playlists would reappear after sync with a new ID.

* Add LIBRARY_PLAYLISTS_EDIT feature to builtin provider

The builtin provider was missing ProviderFeature.LIBRARY_PLAYLISTS_EDIT,
which prevented library_remove() from being called when playlists were
removed from the library. This meant the playlist file cleanup code was
never executed.

Adding this feature ensures that:
1. library_edit_supported() returns True for playlists
2. library_remove() is called during playlist removal
3. The playlist file is properly deleted

* Use playlist lock for file deletion to prevent race conditions

When deleting a playlist file, we now acquire the _playlist_lock to prevent
race conditions with concurrent read/write operations, consistent with
_read_playlist_file_items() and _write_playlist_file_items().

---------

Co-authored-by: Claude <noreply@anthropic.com>
music_assistant/providers/builtin/__init__.py

index e0490e2d65a777d9df9ee476fc0a6a9b99510899..b97cdfde7ebee5aa22df2811555d39ac0467701c 100644 (file)
@@ -85,6 +85,7 @@ SUPPORTED_FEATURES = {
     ProviderFeature.LIBRARY_PLAYLISTS,
     ProviderFeature.LIBRARY_TRACKS_EDIT,
     ProviderFeature.LIBRARY_RADIOS_EDIT,
+    ProviderFeature.LIBRARY_PLAYLISTS_EDIT,
     ProviderFeature.PLAYLIST_CREATE,
     ProviderFeature.PLAYLIST_TRACKS_EDIT,
 }
@@ -342,6 +343,11 @@ class BuiltinProvider(MusicProvider):
         elif media_type == MediaType.PLAYLIST:
             # manually added (multi provider) playlist removal
             key = CONF_KEY_PLAYLISTS
+            # also delete the playlist file if it exists
+            playlist_file = os.path.join(self._playlists_dir, prov_item_id)
+            if await asyncio.to_thread(os.path.isfile, playlist_file):
+                async with self._playlist_lock:
+                    await asyncio.to_thread(os.remove, playlist_file)
         else:
             return False
         stored_items: list[StoredItem] = self.mass.config.get(key, [])