diff --git a/tests/test_service_playlist_export.py b/tests/test_service_playlist_export.py
index e9bc7b8d..056a80fd 100644
--- a/tests/test_service_playlist_export.py
+++ b/tests/test_service_playlist_export.py
@@ -128,3 +128,27 @@ def test_spotify_backfill_search_disables_cross_service_fallback(monkeypatch):
assert fn is not None
fn('Kendrick Lamar', 'Not Like Us') # drives the search
assert seen['allow_fallback'] is False
+
+
+def test_spotify_export_endpoint_demands_auth_when_no_write_scope(monkeypatch):
+ """The export endpoint must return needs_auth (not start a doomed job) when the Spotify
+ token lacks write scope — and it must short-circuit BEFORE touching the DB."""
+ import types
+ monkeypatch.setattr(ws, 'spotify_client',
+ types.SimpleNamespace(has_write_scope=lambda: False))
+ resp = ws.app.test_client().post('/api/playlists/5/export/service/spotify')
+ data = resp.get_json()
+ assert data['needs_auth'] is True
+ assert data['auth_url'] == '/auth/spotify/export'
+ assert data['success'] is False
+
+
+def test_spotify_export_endpoint_proceeds_when_write_scope_present(monkeypatch):
+ """With write scope, the spotify path must NOT short-circuit on needs_auth (it goes on to
+ start a job — here it just must not be a needs_auth response)."""
+ import types
+ monkeypatch.setattr(ws, 'spotify_client',
+ types.SimpleNamespace(has_write_scope=lambda: True))
+ resp = ws.app.test_client().post('/api/playlists/999999/export/service/spotify')
+ data = resp.get_json()
+ assert not data.get('needs_auth')
diff --git a/webui/static/stats-automations.js b/webui/static/stats-automations.js
index 6f2a5c9c..83b551b2 100644
--- a/webui/static/stats-automations.js
+++ b/webui/static/stats-automations.js
@@ -730,11 +730,11 @@ async function _startPlaylistExport(playlistId, mode, name, backfill) {
body: isService ? JSON.stringify({ backfill: !!backfill }) : JSON.stringify({ mode }),
});
const data = await resp.json();
- // Spotify export needs a one-time write-permission grant. Open the consent in a new tab
- // and tell the user to try again once they've authorized.
+ // Spotify export needs a one-time write-permission grant. Surface a clickable link (a
+ // direct user click avoids popup-blocking; window.open after this await would be blocked)
+ // and tell the user to retry once they've authorized.
if (data.needs_auth && data.auth_url) {
- window.open(data.auth_url, '_blank');
- _setExportStatus(playlistId, `Spotify needs permission to create playlists — approve it in the new tab, then click Export again.`, 15000);
+ _setExportStatus(playlistId, `Spotify needs permission to create playlists — authorize, then click Export again.`, 20000);
return;
}
if (!data.success || !data.job_id) {